Повторная выборка кадра данных pandas и интерполяция пропущенных значений для данных временных рядов - PullRequest
1 голос
/ 21 марта 2019

Мне нужно пересчитать данные временных рядов и интерполировать пропущенные значения с 15-минутными интервалами в течение часа.Каждый идентификатор должен иметь четыре строки данных в час.

In:

ID            Time  Value
1   1/1/2019 12:17      3
1   1/1/2019 12:44      2
2   1/1/2019 12:02      5
2   1/1/2019 12:28      7

Out:

ID                Time  Value
1  2019-01-01 12:00:00    3.0
1  2019-01-01 12:15:00    3.0
1  2019-01-01 12:30:00    2.0
1  2019-01-01 12:45:00    2.0
2  2019-01-01 12:00:00    5.0
2  2019-01-01 12:15:00    7.0
2  2019-01-01 12:30:00    7.0
2  2019-01-01 12:45:00    7.0

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

Есть ли более эффективный способ сделать это?

import datetime
import pandas as pd


data = pd.DataFrame({'ID': [1,1,2,2], 
                    'Time': ['1/1/2019 12:17','1/1/2019 12:44','1/1/2019 12:02','1/1/2019 12:28'], 
                    'Value': [3,2,5,7]})


def clean_dataset(data):
    ids = data.drop_duplicates(subset='ID')
    data['Time'] = pd.to_datetime(data['Time'])
    data['Time'] = data['Time'].apply(
    lambda dt: datetime.datetime(dt.year, dt.month, dt.day, dt.hour,15*(dt.minute // 15)))
    data = data.drop_duplicates(subset=['Time','ID']).reset_index(drop=True)
    df = pd.DataFrame(columns=['Time','ID','Value'])
    for i in range(ids.shape[0]):
        times = pd.DataFrame(pd.date_range('1/1/2019 12:00','1/1/2019 13:00',freq='15min'),columns=['Time'])
        id_data = data[data['ID']==ids.iloc[i]['ID']]
        clean_data = times.join(id_data.set_index('Time'), on='Time')
        clean_data = clean_data.interpolate(method='linear', limit_direction='both')
        clean_data.drop(clean_data.tail(1).index,inplace=True)
        df = df.append(clean_data)
    return df


clean_dataset(data)

Ответы [ 2 ]

2 голосов
/ 21 марта 2019

Линейная интерполяция замедляется при большом наборе данных.Наличие цикла в вашем коде также ответственно за большую часть замедления.Все, что можно удалить из цикла и предварительно вычислить, поможет повысить эффективность.Например, если вы предварительно определите фрейм данных, который вы используете для инициализации times, код станет более эффективным на 14%:

times_template = pd.DataFrame(pd.date_range('1/1/2019 12:00','1/1/2019 13:00',freq='15min'),columns=['Time'])
for i in range(ids.shape[0]):
    times = times_template.copy()

Профилирование вашего кода подтверждает, что интерполяция занимает наибольшее количество времени(22,7%), затем идут команды объединения (13,1%), добавления (7,71%) и удаления (7,67%).

1 голос
/ 21 марта 2019

Вы можете использовать:

#round datetimes by 15 minutes
data['Time'] = pd.to_datetime(data['Time'])
minutes = pd.to_timedelta(15*(data['Time'].dt.minute // 15), unit='min')
data['Time'] = data['Time'].dt.floor('H') + minutes

#change date range for 4 values (to `12:45`)
rng = pd.date_range('1/1/2019 12:00','1/1/2019 12:45',freq='15min')
#create MultiIndex and reindex
mux = pd.MultiIndex.from_product([data['ID'].unique(), rng], names=['ID','Time'])
data = data.set_index(['ID','Time']).reindex(mux).reset_index()
#interpolate per groups
data['Value'] = (data.groupby('ID')['Value']
                     .apply(lambda x: x.interpolate(method='linear', limit_direction='both')))
print (data)
   ID                Time  Value
0   1 2019-01-01 12:00:00    3.0
1   1 2019-01-01 12:15:00    3.0
2   1 2019-01-01 12:30:00    2.0
3   1 2019-01-01 12:45:00    2.0
4   2 2019-01-01 12:00:00    5.0
5   2 2019-01-01 12:15:00    7.0
6   2 2019-01-01 12:30:00    7.0
7   2 2019-01-01 12:45:00    7.0

Если диапазон нельзя изменить:

data['Time'] = pd.to_datetime(data['Time'])
minutes = pd.to_timedelta(15*(data['Time'].dt.minute // 15), unit='min')
data['Time'] = data['Time'].dt.floor('H') + minutes

#end in 13:00
rng = pd.date_range('1/1/2019 12:00','1/1/2019 13:00',freq='15min')
mux = pd.MultiIndex.from_product([data['ID'].unique(), rng], names=['ID','Time'])
data = data.set_index(['ID','Time']).reindex(mux).reset_index()
data['Value'] = (data.groupby('ID')['Value']
                     .apply(lambda x: x.interpolate(method='linear', limit_direction='both')))

#remove last row per groups
data = data[data['ID'].duplicated(keep='last')]
print (data)
   ID                Time  Value
0   1 2019-01-01 12:00:00    3.0
1   1 2019-01-01 12:15:00    3.0
2   1 2019-01-01 12:30:00    2.0
3   1 2019-01-01 12:45:00    2.0
5   2 2019-01-01 12:00:00    5.0
6   2 2019-01-01 12:15:00    7.0
7   2 2019-01-01 12:30:00    7.0
8   2 2019-01-01 12:45:00    7.0

РЕДАКТИРОВАТЬ:

Другое решение с merge и левым соединением вместо * reindex:

from  itertools import product

#round datetimes by 15 minutes
data['Time'] = pd.to_datetime(data['Time'])
minutes = pd.to_timedelta(15*(data['Time'].dt.minute // 15), unit='min')
data['Time'] = data['Time'].dt.floor('H') + minutes

#change date range for 4 values (to `12:45`)
rng = pd.date_range('1/1/2019 12:00','1/1/2019 12:45',freq='15min')
#create helper DataFrame and merge with left join
df = pd.DataFrame(list(product(data['ID'].unique(), rng)), columns=['ID','Time'])
print (df)
   ID                Time
0   1 2019-01-01 12:00:00
1   1 2019-01-01 12:15:00
2   1 2019-01-01 12:30:00
3   1 2019-01-01 12:45:00
4   2 2019-01-01 12:00:00
5   2 2019-01-01 12:15:00
6   2 2019-01-01 12:30:00
7   2 2019-01-01 12:45:00

data = df.merge(data, how='left')
##interpolate per groups
data['Value'] = (data.groupby('ID')['Value']
                     .apply(lambda x: x.interpolate(method='linear', limit_direction='both')))
print (data)
   ID                Time  Value
0   1 2019-01-01 12:00:00    3.0
1   1 2019-01-01 12:15:00    3.0
2   1 2019-01-01 12:30:00    2.0
3   1 2019-01-01 12:45:00    2.0
4   2 2019-01-01 12:00:00    5.0
5   2 2019-01-01 12:15:00    7.0
6   2 2019-01-01 12:30:00    7.0
7   2 2019-01-01 12:45:00    7.0
...