Я хочу рассчитать количество прогрессов по выполнению домашних работ в каждом месяце
за всю историю (каждый месяц в диапазоне от июня 2018 до февраля 2019 включительно)
для каждого курса.
Необходимо учитывать, что прогресс выполнения домашней работы может перетекать из
одного месяца в другой (такие прогрессы надо включать в общее число прогрессов для
всех месяцев, которые покрывает срок выполнения этих домашних работ).
progress_id – id прогресса,
course – название курса,
start – начало выполнения домашней работы,
finish – конец выполнения домашней работы.
list_month = ['2018-06', '2018-07', '2018-08',
'2018-09', '2018-10', '2018-11',
'2018-12', '2019-01', '2019-02']
df_table = pd.DataFrame({'progress_id': ['progress1', 'progress2',
'progress3', 'progress4',
'progress5', 'progress6'],
'course': ['course1', 'course2', 'course1',
'course3', 'course3', 'course4'],
'start': ['2018-06', '2018-07', '2018-07',
'2018-10', '2018-09', '2018-12'],
'finish': ['2018-08', '2018-07', '2018-10',
'2018-12', '2018-11', '2019-02']
},
columns = ['progress_id','course',
'start', 'finish'])
Попытка сделать добавление новых строк в конец датафрейма ни к чему не привела:
попытка сделать цикл while df_table['start'] != df_table['finish'] с наращиванием
через счетчик значения месяца 'start' до тех пор пока он не будет равен месяцу 'finish'
и добавление нового "промежуточного" месяца в колонку 'finish'.
Подскажите пожалуйста, как это можно сделать корректно?
Ответы
Ответ 1
df_table.groupby('course') \
.apply(lambda course: \
course.apply(lambda x: \
pd.Series(1, index=pd.date_range(x['start'], x['finish'], closed=None,
freq='MS')) \
, axis=1) \
.sum(axis=0)) \
.reset_index(level=0).pivot(columns='course', values=0) \
.reindex(pd.date_range(df_table.start.min(), df_table.finish.max(), freq='MS'),
fill_value=np.nan)
course course1 course2 course3 course4
2018-06-01 1.0 NaN NaN NaN
2018-07-01 2.0 1.0 NaN NaN
2018-08-01 2.0 NaN NaN NaN
2018-09-01 1.0 NaN 1.0 NaN
2018-10-01 1.0 NaN 2.0 NaN
2018-11-01 NaN NaN 2.0 NaN
2018-12-01 NaN NaN 1.0 1.0
2019-01-01 NaN NaN NaN 1.0
2019-02-01 NaN NaN NaN 1.0
Вот немного о том. как это работает
Самый внутренний apply превращает каждую строку в Series вот такого вида
2018-12-01 1
2019-01-01 1
2019-02-01 1
Внешний собирает их все по курсам
course
course1 2018-06-01 1.0
2018-07-01 2.0
2018-08-01 2.0
2018-09-01 1.0
2018-10-01 1.0
course2 2018-07-01 1.0
course3 2018-09-01 1.0
...
Дальше строим сводную таблицу
В последней строке заполняем возможные пропуски индекса.
Ответ 2
Вся программа (после импортов и создания вашей таблицы):
for dt in pd.date_range("2018-06-01", "2019-02-01", freq="MS"):
new_column = str.format("{}-{:02d}", dt.year, dt.month)
df_table[new_column] = 0
for ind, row in df_table.iterrows():
start = row["start"] + "-01"
finish = row["finish"] + "-01"
dtRange = pd.date_range(start, finish, freq="MS")
for dt in dtRange:
colName = str.format("{}-{:02d}", dt.year, dt.month)
df_table.loc[ind, colName] = 1
print(df_table.sum(numeric_only=True))
Вывод:
2018-06 1
2018-07 3
2018-08 2
2018-09 2
2018-10 3
2018-11 2
2018-12 2
2019-01 1
2019-02 1
dtype: int64
Объяснение:
Главная идея:
Как перечислить все месяцы между (например) 2018-12 и 2019-03?
Так что построим объект типа DatetimeIndex с месячной частотой, с первого дня в месяце:
In[3]: pd.date_range("2018-12-01", "2019-02-01", freq="MS")
Out[3]: DatetimeIndex(['2018-12-01', '2019-01-01', '2019-02-01', '2019-03-01'], dtype='datetime64[ns]',
freq='MS')
А теперь постепенно.
Ваша исходная таблица:
In[61]: df_table
Out[61]:
progress_id course start finish
0 progress1 course1 2018-06 2018-08
1 progress2 course2 2018-07 2018-07
2 progress3 course1 2018-07 2018-10
3 progress4 course3 2018-10 2018-12
4 progress5 course3 2018-09 2018-11
5 progress6 course4 2018-12 2019-02
Добавим колонки для всея индивидуальных месяцев в вами требуемом диапазоне:
In[62]: for dt in pd.date_range("2018-06-01", "2019-02-01", freq="MS"):
...: new_column = str.format("{}-{:02d}", dt.year, dt.month)
...: df_table[new_column] = 0
...:
In[63]: df_table
Out[63]:
progress_id course start finish 2018-06 2018-07 2018-08 2018-09 \
0 progress1 course1 2018-06 2018-08 0 0 0 0
1 progress2 course2 2018-07 2018-07 0 0 0 0
2 progress3 course1 2018-07 2018-10 0 0 0 0
3 progress4 course3 2018-10 2018-12 0 0 0 0
4 progress5 course3 2018-09 2018-11 0 0 0 0
5 progress6 course4 2018-12 2019-02 0 0 0 0
2018-10 2018-11 2018-12 2019-01 2019-02
0 0 0 0 0 0
1 0 0 0 0 0
2 0 0 0 0 0
3 0 0 0 0 0
4 0 0 0 0 0
5 0 0 0 0 0
В цикле для каждой строки таблицы сделаем объект типа DatetimeIndex в диапазоне значений
в колонках start и finish, а затем для всякого его элемента в этом объекте запишем
значение 1 для соответствующей колонки:
In[64]: for ind, row in df_table.iterrows():
...: start = row["start"] + "-01"
...: finish = row["finish"] + "-01"
...: dtRange = pd.date_range(start, finish, freq="MS")
...: for dt in dtRange:
...: colName = str.format("{}-{:02d}", dt.year, dt.month)
...: df_table.loc[ind, colName] = 1
...:
In[65]: df_table
Out[65]:
progress_id course start finish 2018-06 2018-07 2018-08 2018-09 \
0 progress1 course1 2018-06 2018-08 1 1 1 0
1 progress2 course2 2018-07 2018-07 0 1 0 0
2 progress3 course1 2018-07 2018-10 0 1 1 1
3 progress4 course3 2018-10 2018-12 0 0 0 0
4 progress5 course3 2018-09 2018-11 0 0 0 1
5 progress6 course4 2018-12 2019-02 0 0 0 0
2018-10 2018-11 2018-12 2019-01 2019-02
0 0 0 0 0 0
1 0 0 0 0 0
2 1 0 0 0 0
3 1 1 1 0 0
4 1 1 0 0 0
5 0 0 1 1 1
Но а наконец применим метод .sum():
In[66]: df_table.sum(numeric_only=True)
Out[66]:
2018-06 1
2018-07 3
2018-08 2
2018-09 2
2018-10 3
2018-11 2
2018-12 2
2019-01 1
2019-02 1
dtype: int64
Ответ 3
Вся программа (после импортов и создания вашей таблицы):
df_table["Months"] = 0 # Добавим новую колонку для количества месяцев
for ind, row in df_table.iterrows(): # Проходим строками таблицы
start = row["start"] + "-01" # После года и месяца добавим и день в месяце
finish = row["finish"] + "-01"
df_table.loc[ind, "Months"] = len(pd.date_range(start, finish, freq="MS")) #
см. далее
print(df_table.groupby("course").sum())
Вывод:
Months
course
course1 7
course2 1
course3 6
course4 3
Объяснение:
Как посчитать, сколько месяцев между (например) 2018-12 и 2019-02 (ваша последняя
строка в таблице)?
Так:
Построим DatetimeIndex с месячной частотой, от первого дня в месяце:
In[3]: pd.date_range("2018-12-01", "2019-02-01", freq="MS")
Out[3]: DatetimeIndex(['2018-12-01', '2019-01-01', '2019-02-01'], dtype='datetime64[ns]',
freq='MS')
Посчитаем, сколько в нем элементов:
In[4]: len(_)
Out[4]: 3
Теперь то же самое с вашей таблицей df_table, добавляя вычисленное значение в новый
столбец:
In[6]: df_table["Months"] = 0
In[7]: for ind, row in df_table.iterrows():
...: start = row["start"] + "-01"
...: finish = row["finish"] + "-01"
...: df_table.loc[ind, "Months"] = len(pd.date_range(start, finish, freq="MS"))
...:
In[8]: df_table
Out[8]:
progress_id course start finish Months
0 progress1 course1 2018-06 2018-08 3
1 progress2 course2 2018-07 2018-07 1
2 progress3 course1 2018-07 2018-10 4
3 progress4 course3 2018-10 2018-12 3
4 progress5 course3 2018-09 2018-11 3
5 progress6 course4 2018-12 2019-02 3
И наконец применим метод groupby():
In[9]: df_table.groupby("course").sum()
Out[9]:
Months
course
course1 7
course2 1
course3 6
course4 3