Страницы

Поиск по вопросам

четверг, 9 января 2020 г.

Диапазон покрытия по всему сроку Python Pandas

#python #pandas


Я хочу рассчитать количество прогрессов по выполнению домашних работ в каждом месяце
за всю историю (каждый месяц в диапазоне от июня 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

Комментариев нет:

Отправить комментарий