Pandas: подсчет дней в каждом месяце между заданной датой начала и окончания - PullRequest
0 голосов
/ 27 марта 2020

У меня есть pandas фрейм данных с некоторыми датами начала и окончания.

ActualStartDate ActualEndDate
0   2019-06-30  2019-08-15
1   2019-09-01  2020-01-01
2   2019-08-28  2019-11-13

Учитывая эти даты начала и окончания, мне нужно посчитать, сколько дней в каждом месяце между датами начала и окончания. Я не могу найти хороший способ приблизиться к этому, но итоговый фрейм данных должен выглядеть примерно так:

ActualStartDate ActualEndDate 2019-06 2019-07 2019-08 2019-09 2019-10 2019-11 2019-12 2020-01 etc
0   2019-06-30  2019-08-15    1       31      15      0       0       0       0       0
1   2019-09-01  2020-01-01    0       0       0       30      31      30      31      1
2   2019-08-28  2019-11-13    0       0       4       30      31      13      0       0

Обратите внимание, что фактический фрейм данных имеет ~ 1500 строк с разными датами начала и окончания. Открыт для другого вывода df, но показывает выше, чтобы дать вам представление о том, что мне нужно сделать sh. Заранее благодарю за любую помощь!

Ответы [ 3 ]

1 голос
/ 27 марта 2020

Вероятно, не самый эффективный, но не должен быть слишком плох для ~ 1500 строк ... расширить диапазон дат, а затем преобразовать его в месячный период, подсчитать их и вернуться к исходному DF, например :

res = df.join(
    df.apply(lambda v: pd.Series(pd.date_range(v['ActualStartDate'], v['ActualEndDate'], freq='D').to_period('M')), axis=1)
    .apply(pd.value_counts, axis=1)
    .fillna(0)
    .astype(int)
)

Дает вам:

  ActualStartDate ActualEndDate  2019-06  2019-07  2019-08  2019-09  2019-10  2019-11  2019-12  2020-01  2020-02  2020-03  2020-04  2020-05  2020-06  2020-07  2020-08  2020-09  2020-10  2020-11
0      2019-06-30    2020-08-15        1       31       31       30       31       30       31       31       29       31       30       31       30       31       15        0        0        0
1      2019-09-01    2020-01-01        0        0        0       30       31       30       31        1        0        0        0        0        0        0        0        0        0        0
2      2019-08-28    2020-11-13        0        0        4       30       31       30       31       31       29       31       30       31       30       31       31       30       31       13
1 голос
/ 27 марта 2020

Идея состоит в том, чтобы создать месячные периоды по DatetimeIndex.to_period из date_range и рассчитать по Index.value_counts, а затем создать DataFrame по concat с заменой отсутствующих значений на DataFrame.fillna, последнее соединение с оригиналом на DataFrame.join:

L = {r.Index: pd.date_range(r.ActualStartDate, r.ActualEndDate).to_period('M').value_counts()
     for r in df.itertuples()}
df = df.join(pd.concat(L, axis=1).fillna(0).astype(int).T)
print (df)
  ActualStartDate ActualEndDate  2019-06  2019-07  2019-08  2019-09  2019-10  \
0      2019-06-30    2019-08-15        1       31       15        0        0   
1      2019-09-01    2020-01-01        0        0        0       30       31   
2      2019-08-28    2019-11-13        0        0        4       30       31   

   2019-11  2019-12  2020-01  
0        0        0        0  
1       30       31        1  
2       13        0        0  

Производительность :

df = pd.concat([df] * 1000, ignore_index=True)

In [44]: %%timeit
    ...: L = {r.Index: pd.date_range(r.ActualStartDate, r.ActualEndDate).to_period('M').value_counts()
    ...:      for r in df.itertuples()}
    ...: df.join(pd.concat(L, axis=1).fillna(0).astype(int).T)
    ...: 
689 ms ± 5.63 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [45]: %%timeit
    ...: df.join(
    ...:     df.apply(lambda v: pd.Series(pd.date_range(v['ActualStartDate'], v['ActualEndDate'], freq='D').to_period('M')), axis=1)
    ...:     .apply(pd.value_counts, axis=1)
    ...:     .fillna(0)
    ...:     .astype(int))
    ...:     
994 ms ± 5.17 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
0 голосов
/ 27 марта 2020
import pandas as pd
import calendar

date_info = pd.DataFrame({
    'ActualStartDate': [
        pd.Timestamp('2019-06-30'),
        pd.Timestamp('2019-09-01'),
        pd.Timestamp('2019-08-28'),
    ],
    'ActualEndDate': [
        pd.Timestamp('2019-08-15'),
        pd.Timestamp('2020-01-01'),
        pd.Timestamp('2019-11-13'),
    ]
})

# ============================================================

result = {}  # result should in dict, in case of too many cols.
for index, timepair in date_info.iterrows():
    start = timepair['ActualStartDate']
    end = timepair['ActualEndDate']

    current  = start
    result[index] = {}  # delta days in this pair
    while True:
        # find the delta days
        # current day is also count, so should + 1
        _, days = calendar.monthrange(current.year, current.month)
        days = min(days, (end - current).days + 1)
        delta = days - current.day + 1

        result[index]['%s-%s'%(current.year, current.month)] = delta
        current += pd.Timedelta(delta, unit='d')

        if current >= end:
            break

# you can save the result in dataframe, if you insisit
columns = set()
for value in result.values():
    columns.update(value.keys())

for col in columns:
    date_info[col] = 0

for index, delta in result.items():
    for date, days in delta.items():
        date_info.loc[index, date] = days

print(date_info)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...