I am getting input as start_date and end_date, now I want to fetch data based on months between start_date and end_date. How that can be done in python/django?
I am getting date in format as -
start_date = '2021-5-5' #YYYY-MM-DD format
end_date = '2021-6-5'
Required results -
result = [
{
'month' : 'may',
'data' : data_for_may # from date 5th of may to 31st of may
},
{
'month' : 'june',
'data' : data_for_june # from date 1st of june to 5th of june
}
]
I think you're better off doing:
from datetime import datetime
from django.db.models import Count
from django.contrib.auth.models import User
start_date = datetime.strptime('2021-5-5' , '%Y-%m-%d')
month_end_date = datetime.strptime('2021-6-5' , '%Y-%m-%d')
# SELECT year(last_login), month(last_login), count(*)
# FROM auth_user
# GROUP BY year(last_login), month(last_login)
# ORDER BY year(last_login), month(last_login)
qs = (User.objects.values('last_login__month', 'last_login__year')
.annotate(data=Count('*'))
.order_by('last_login__year', 'last_login__month'))
# WHERE last_login ...
qs = qs.filter(last_login__range=[start_date, month_end_date])
result = []
for item in qs:
result.append({
# get pretty name i.e "January"
'month': datetime(1900, item['last_login__month'] , 1).strftime('%B'),
'data': item['data']
})
result # [{'month': 'May', 'data': 81}, {'month': 'June', 'data': 15}])
Why do I think this is better? (over the other answers provided)
You will only have 1 record PER month PER year, easy to quantify/predict, better on performance.
I wrote tests for you by the way ;)
https://gist.github.com/kingbuzzman/0197da03c52ae9a798c99d0cf58c758c#file-month_data-py-L82-L133
As a comment inside the gist, I provide examples on how to test it using docker
Depending on how much data you have, I would fetch all data in a single query, ordered by datetime, and then group them in Python. The following snippet illustrates that idea.
from itertools import groupby
data = User.objects.order_by('last_login')
result = []
for (year, month), data_per_month in groupby(data, key=lambda x: (x.last_login.year(), x.last_login.month())):
result.append({
'year': year,
'month': month,
'data': data_per_month
})
This will probably be fast and easily fitting in memory with 10,000s of objects. When fetching millions of records though, you might need to reconsider.
First, you need to convert your strings into dates :
start_date = datetime.strptime(start_date , '%Y-%m-%d')
end_date = datetime.strptime(end_date , '%Y-%m-%d')
Then you can iterate on each month to populate your results array :
start_date = datetime.datetime.strptime(start_date, '%Y-%m-%d')
end_date = datetime.datetime.strptime(end_date, '%Y-%m-%d')
all_data = User.objects.filter(last_login__date__range=[start_date, end_date])
results = []
while start_date.year < end_date.year or start_date.month <= end_date.month:
results.append({
'month': start_date.strftime('%B'),
'year': start_date.strftime('%Y'),
'data': all_data.filter(last_login__date__month=start_date.month, last_login__date__year=start_date.year)
})
# Increment the start_date by one month using the dateutil library
start_date = start_date + dateutil.relativedelta(months=+1)
NOTE : I edited my first idea for this solution and tested it in my Django project.
Related
What I am trying to figure out is how to add "Cases" and "Deaths" for each day, so that it starts with: "1/19/2020 Cases" and "1/19/2020 Deaths" then "1/20/2020 Cases" etc. It seems the append function does not work for this, and I don't know how else to add this. It doesn't seem like python has a way to do this task. My eventual goal is to make this a pandas dataframe.
import pandas as pd
dates = pd.date_range(start = '1/19/2020', end = '12/31/2021')
lst = dates.repeat(repeats = 2)
print(lst)
Thanks
If I am not mistaken, I don't think there's a way to do it with purely pandas. However with python and datetime, you can do so:
import pandas as pd
from datetime import timedelta, date
def daterange(start_date, end_date):
# Credit: https://stackoverflow.com/a/1060330/10640517
for n in range(int((end_date - start_date).days)):
yield start_date + timedelta(n)
dates = []
start_date = date(2020, 1, 19) # Start date here
end_date = date(2021, 12, 31) # End date here
for single_date in daterange(start_date, end_date):
dates.append(single_date.strftime("%m/%d/%Y") + " Cases")
dates.append(single_date.strftime("%m/%d/%Y") + " Deaths")
pdates = pd.DataFrame(dates)
print (pdates)
Is this what you want? If not, I can delete it.
Say for the year of 2020, how do I iterate through the days in the months so that my outcome would be in the following format:
Jan1
Jan2
Jan3
....
Jan31
Feb1
I've tried so many things online but I couldnt find an answer. Please help :(
Both of these methods will handle leap years correctly out of the box.
Using a simple while loop:
from datetime import datetime, timedelta
def iter_days(year):
dt = datetime(year, 1, 1)
while dt.year == year:
yield dt
dt += timedelta(days=1)
Using date rules:
from datetime import datetime
from dateutil.rrule import rrule, DAILY
def iter_days(year):
first_date = datetime(year, 1, 1)
last_date = datetime(year, 12, 31)
return rrule(DAILY, dtstart=first_date, until=last_date)
Both would be used the same:
for dt in iter_days(2020):
print(dt.strftime('%b%-d'))
The format string '%b%-d' will give you the format you specified in your question. I don't know if that was a requirement or not.
This is crude but gets what you want for 2020. You'll need to change 366 to 365 for non-leap-years.
#!/usr/bin/python3
import datetime
startDate = '2020-01-01'
start = datetime.datetime.strptime(startDate, '%Y-%m-%d')
for dayNum in range(0,366):
dayOfYear = start + datetime.timedelta(days=dayNum)
print(dayOfYear.strftime('%b %d, %Y'))
The calendar module offers quite a bit of functionality.
Here is a solution that works for any given year
import calendar as cal
for mi in range(1,13):
_, days = cal.monthrange(2020, mi)
for d in range(1, days+1):
print(cal.month_name[mi], d)
I have two tables:
-event dates
-return dates
Some event dates are not at a trading day.
How can I change the event date to the next trading day?
So if event date is not in return dates, take the next day in return dates.
The approach to change weekend days to working days does not work because of days like Christmas.
The best would be to look up the next day in the return table.
for i in event['date']:
if i is not in return ['date'].values:
event ['date']=i+datetime.timedelta(days=1)
but this doenst work
I am working with dataframes and dates have the format datetime64[ns]. If the event date does not exist in return date than event date plus one day
Edit
After the clarifications concerning the desired logic, here is the new solution
from datetime import datetime, timedelta
import numpy as np
import pandas as pd
# Create two df
event_date = datetime.now()
event_dates = pd.DataFrame([datetime(2020, 2, _) for _ in range(1, 29)], columns=['date'])
print(event_dates.date[0])
# 2020-02-01 00:00:00
return_dates = pd.DataFrame([datetime(2020, 1, _) for _ in range(1, 32)], columns=['date'])
# Apply logic
event_dates.date = [_ if _ in return_dates.date else _ + timedelta(days=1) for _ in event_dates.date]
print(event_dates.date[0])
# 2020-02-02 00:00:00
Base Python
Here is a solution using the standard datetime library
from datetime import datetime
from typing import List
def get_next_trade_date(date: datetime, date_list: List[datetime]) -> datetime: # The annotations here are just to specify the types of the objects
if date in date_list: # Check if the date is contained in the list
return date
delta, res = None, None # Initialize both to None
for _ in date_list:
tmp = abs((date - _).days) # Time difference in current iteration
if not delta or tmp < delta: # See bullet point 1.
delta, res = tmp, _
return res
if __name__ == '__main__':
event_date = datetime.now()
return_dates = [datetime(2020, 1, _) for _ in range(1, 32)]
print(get_next_trade_date(event_date, return_dates))
# 2020-01-01 00:00:00
Notice that
The condition not delta or tmp < delta is twofold: in the first iteration delta, res are both None so we will overwrite them with tmp, _. We catch this by using not delta. The other part (tmp < delta) is more obvious: if we have a new minimal delta then we overwrite delta, res.
I only considered days intervals ((date - _).days), you could go further into details (see datetime.timedelta for more info)
coming from R I believe there must be a simpler solution using numpy - see below
Numpy
This solution uses numpy. (date_list - date) is an array of timedeltas, (date_list - date).argmin() returns the index of the minimal value.
from datetime import datetime
import numpy as np
def get_next_trade_date(date: datetime, date_list: np.ndarray) -> datetime:
return date_list[(date_list - date).argmin()]
if __name__ == '__main__':
event_date = datetime.now()
return_dates = np.array([datetime(2020, 1, _) for _ in range(1, 32)])
print(get_next_trade_date(event_date, return_dates))
# 2020-01-01 00:00:00
The problem is the dataset has variable data rates per ID, I would like to filter out the IDs that do not have at least one data point per day.
I have a dataframe with IDs, dates, and data, in which I counted the daily sampling rate for each ID.
dfcounted = df.reset_index().groupby(['id', pd.Grouper(key='datetime', freq='D')]).count().reset_index()
Now, i have taken the first and last date of the dataframe, and created a dataframe of each day between the starting and ending dates:
# take dates
sdate = df['datetime'].min() # start date
edate = df['datetime'].max() # end date
# interval
delta = edate - sdate # as timedelta
# empty list
dates = []
# store each date in list
for i in range(delta.days + 1):
day = sdate + timedelta(days=i)
dates.append(day)
# convert to dataframe
dates = pd.DataFrame(data = dates, columns=["date"])
From here, I am lost on how to proceed. I have created a sample dataframe
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import random
import string
letters = string.ascii_lowercase
ids = random.choices(letters,k=100)
date_today = datetime.now()
days = pd.date_range(date_today, date_today + timedelta(99), freq='D')
np.random.seed(seed=1111)
data = np.random.randint(1, high=100, size=len(days))
df = pd.DataFrame({'date': days,'ids': ids, 'data': data})
df = df.set_index('date')
With the sample df, i would expect to create a "results" df with only the ids that have data in each date.
I have a pandas dataframe that looks like:
import pandas as pd
df1 = pd.DataFrame({'Counterparty':['Bank','Client','Bank','Bank','Bank','Bank'],
'Date':['4Q18','1Q19','2Q19','4Q21','FY22','H123']
})
I want to convert the 'Date' column from a string to a date such that the date is the last date for that particular period. ie 'FQ18'= 31st Dec 2018, '1Q19' = 31st Mar 2019, 'FY22' = 31st Dec 2022,'H123'= 30th June 2023
Any suggestions how to achieve this ?
As mentioned by #jpp, you're going to have to do some customization. There isn't existing functionality to map "FY22" to 2022-12-31, to my knowledge. Here's something to get you started, based on the limited example you've shown:
import re
import pandas as pd
from pandas.core.tools.datetimes import DateParseError
from pandas.tseries import offsets
halfyr = re.compile(r'H(?P<half>\d)(?P<year>\d{2})')
fiscalyr = re.compile(r'FY(?P<year>\d{2})')
def try_qend(date):
try:
return pd.to_datetime(date) + offsets.QuarterEnd()
except (DateParseError, ValueError):
halfyr_match = halfyr.match(date)
if halfyr_match:
half, year = [int(i) for i in halfyr_match.groups()]
month = 6 if half == 1 else 12
return pd.datetime(2000 + year, month, 1) + offsets.MonthEnd()
else:
fiscalyr_match = fiscalyr.match(date)
if fiscalyr_match:
year = int(fiscalyr_match.group('year'))
return pd.datetime(2000 + year, 12, 31)
else:
# You're SOL
return pd.NaT
def parse_dates(dates):
return pd.to_datetime([try_qend(date) for date in dates])
Assumptions:
All years are 20yy, not 19xx.
The regex patterns here completely describe the year-half/fiscal-year syntax set.
Example:
dates = ['4Q18','1Q19','2Q19','4Q21','FY22','H123']
parse_dates(dates)
DatetimeIndex(['2018-12-31', '2019-03-31', '2019-06-30', '2021-12-31',
'2022-12-31', '2023-06-30'],
dtype='datetime64[ns]', freq=None)