Вставка дней в диапазоне на группу - PullRequest
0 голосов
/ 05 декабря 2018

У меня есть проблема, которую, вероятно, можно решить с помощью MultiIndex, reindex и period_range, но я не представляю, как именно это сделать.

У меня есть следующий фрейм данных:

proj_id   date_from    date_to      some_value
abc1001   2017-10-20   2017-10-21            7
abc1002   2017-10-29   2017-11-03           10
abc1002   2017-09-05   2017-09-07            9
abc1003   2017-09-05   2017-09-05            3

Я бы хотел преобразовать его в что-то вроде этого:

proj_id   date         some_value
abc1001   2017-10-20            7
abc1001   2017-10-21            7
abc1002   2017-10-29           10
abc1002   2017-10-30           10
abc1002   2017-10-31           10
abc1002   2017-11-01           10
abc1002   2017-11-02           10
abc1002   2017-11-03           10
abc1002   2017-09-05            9
abc1002   2017-09-06            9
abc1002   2017-09-07            9
abc1003   2017-09-05            3

Так что в основном у меня есть несколько диапазонов дат для проекта с некоторым значением и я хочу создавать записи для каждого дня между этими диапазонами, НО диапазоныотличаются для каждого проекта, и даже может быть много отдельных диапазонов для одного проекта.

Я думал, что если бы мне удалось каким-то образом построить MultIindex, например, так:

                    some_value
abc1001 2017-10-20           7
        2017-10-21         NaN
abc1002 2017-09-05           9
        2017-09-06         NaN
        2017-09-07         NaN
        2017-10-29          10
        2017-10-30         NaN
        2017-10-31         NaN
        2017-11-01         NaN
        2017-11-02         NaN
        2017-11-03         NaN
abc1003 2017-09-05           3

, то я был быМожно заполнить пропущенные значения, используя DataFrame.fillna(method='ffill'), но проблема в том, что я не знаю, как создать такой индекс.

Конечно, это только упрощенный пример, и на самом деле количество проектов велико.

Ответы [ 2 ]

0 голосов
/ 05 декабря 2018

jazrael первый ответ правильный, но я либо неправильно портировал его код (потому что, как я сказал, пример был упрощенной версией реальной проблемы), либо у него действительно есть некоторые проблемы с производительностью.Во всяком случае, я реализовал другое решение, которое работает достаточно быстро для меня.Размещение здесь, если кто-то заинтересован:

non_start_end_cols = [col for col in df.columns if col not in ['date_from', 'date_to']]

rows = []
def process_row(row):
    non_date_row_data = [row[col] for col in non_start_end_cols]
    for day in pd.date_range(start=row['date_from'], end=row['date_to']).to_pydatetime():
        rows.append(non_date_row_data + [day])

_ = df.apply(process_row, axis=1)
new_df = pd.DataFrame(rows, columns=non_start_end_cols + ['date'])

А если периоды дат перекрываются, простое затруднение исправляет ситуацию:

groupby_cols = non_start_end_cols.copy()
groupby_cols.append('date')
groupby_cols.remove('some_value')

aggregated = new_df \
    .groupby(groupby_cols) \
    .agg(np.sum) \
    .reset_index()
0 голосов
/ 05 декабря 2018

Используйте melt для изменения формы DataFrame, затем groupby с first и последним вызовом ffill:

Примечание:

Решение работает, если в данных отсутствуют пропущенные значения.

df = (df.reset_index()
        .melt(['proj_id','some_value', 'index'], value_name='date')
        .set_index('date')
        .groupby(['proj_id', 'index'])['some_value']
        .resample('d')
        .first()
        .reset_index(level=1, drop=True)
        .ffill()
        .reset_index()
       )
print (df)
    proj_id       date  some_value
0   abc1001 2017-10-20         7.0
1   abc1001 2017-10-21         7.0
2   abc1002 2017-10-29        10.0
3   abc1002 2017-10-30        10.0
4   abc1002 2017-10-31        10.0
5   abc1002 2017-11-01        10.0
6   abc1002 2017-11-02        10.0
7   abc1002 2017-11-03        10.0
8   abc1002 2017-09-05         9.0
9   abc1002 2017-09-06         9.0
10  abc1002 2017-09-07         9.0
11  abc1003 2017-09-05         3.0

Другое решение:

s = pd.concat([pd.Series(r.Index,pd.date_range(r.date_from, r.date_to)) 
               for r in df.itertuples()])

df1 = df[['proj_id','some_value']].join(pd.Series(s.index, s.values).rename('date'))
print (df1)
   proj_id  some_value       date
0  abc1001           7 2017-10-20
0  abc1001           7 2017-10-21
1  abc1002          10 2017-10-29
1  abc1002          10 2017-10-30
1  abc1002          10 2017-10-31
1  abc1002          10 2017-11-01
1  abc1002          10 2017-11-02
1  abc1002          10 2017-11-03
2  abc1002           9 2017-09-05
2  abc1002           9 2017-09-06
2  abc1002           9 2017-09-07
3  abc1003           3 2017-09-05
...