Я пытаюсь расширить фрейм данных, содержащий несколько столбцов, создавая строки на основе интервала между двумя столбцами даты.
Для этого я в настоящее время использую метод, который в основном создает декартово произведение, которое хорошо работает с небольшими наборами данных, но не подходит для больших наборов, потому что оно очень неэффективно.
Этот метод будет использоваться для ~ 2 миллионов строк на 50 столбцов Dataframe, охватывающих несколько лет от минимальной до максимальной даты. В результирующем наборе данных будет около 3 миллионов строк, поэтому требуется более эффективный подход.
Мне не удалось найти альтернативный метод, менее ресурсоемкий. Что было бы лучшим подходом для этого?
Мой текущий метод здесь:
from datetime import date
import pandas as pd
raw_data = {'id': ['aa0', 'aa1', 'aa2', 'aa3'],
'number': [1, 2, 2, 1],
'color': ['blue', 'red', 'yellow', "green"],
'date_start': [date(2022,1,1), date(2022,1,1), date(2022,1,7), date(2022,1,12)],
'date_end': [date(2022,1,2), date(2022,1,4), date(2022,1,9), date(2022,1,14)]}
df = pd.DataFrame(raw_data)
Теперь, чтобы создать набор, содержащий все возможные даты между минимальной и максимальной датой набора:
df_d = pd.DataFrame({'date': pd.date_range(df['date_start'].min(), df['date_end'].max() + pd.Timedelta('1d'), freq='1d')})
Это приводит к ожидаемому кадру, содержащему все возможные даты.
Наконец, чтобы объединить исходный набор с набором дат и отфильтровать полученные строки на основе даты начала и окончания для каждой строки.
df_total = pd.merge(df, df_d,how='cross')
df = df_total[(df_total['date_start']<df_total['date']) & (df_total['date_end']>=df_total['date']) ]
Это приводит к следующему окончательному
Этот окончательный фрейм данных — именно то, что нужно.
d = df['date_end'].sub(df['date_start']).dt.days
df1 = df.reindex(df.index.repeat(d))
i = df1.groupby(level=0).cumcount() + 1
df1['date'] = df1['date_start'] + pd.to_timedelta(i, unit='d')
Вычтите начало из конца, чтобы рассчитать количество прошедших дней, затем reindex
фрейм данных, повторив индекс точное количество прошедших дней. Теперь сгруппируйте df1
по index
и используйте cumcount
, чтобы создать последовательный счетчик, затем создайте серию timedelta
, используя этот счетчик, и добавьте это с date_start
, чтобы получить результат.
Результат
id number color date_start date_end date
0 aa0 1 blue 2022-01-01 2022-01-02 2022-01-02
1 aa1 2 red 2022-01-01 2022-01-04 2022-01-02
1 aa1 2 red 2022-01-01 2022-01-04 2022-01-03
1 aa1 2 red 2022-01-01 2022-01-04 2022-01-04
2 aa2 2 yellow 2022-01-07 2022-01-09 2022-01-08
2 aa2 2 yellow 2022-01-07 2022-01-09 2022-01-09
3 aa3 1 green 2022-01-12 2022-01-14 2022-01-13
3 aa3 1 green 2022-01-12 2022-01-14 2022-01-14
Я не знаю, является ли это утверждением, здесь pd.date_range
создается только для каждой даты начала и окончания в каждой строке. созданный список взорвется и соединится с исходным df
from datetime import date
import pandas as pd
raw_data = {'id': ['aa0', 'aa1', 'aa2', 'aa3'],
'number': [1, 2, 2, 1],
'color': ['blue', 'red', 'yellow', "green"],
'date_start': [date(2022,1,1), date(2022,1,1), date(2022,1,7), date(2022,1,12)],
'date_end': [date(2022,1,2), date(2022,1,4), date(2022,1,9), date(2022,1,14)]}
df = pd.DataFrame(raw_data)
s = df.apply(lambda x: pd.date_range(x['date_start'], x['date_end'], freq='1d',inclusive='right').date,axis=1).explode()
df.join(s.rename('date'))