Среднее за последние 2 месяца - PullRequest
4 голосов
/ 07 октября 2019

Учитывая приведенную ниже таблицу, в которой указаны только столбцы date и number, я хочу вывести столбец avg_last_2m (который рассчитывает среднее значение для столбца number за последние 2 месяца), который представлен следующим образом:

enter image description here

Например, учитывая дату 20190909, последние 2 месяца будут начинаться с даты 20190709 до даты 20190908 в течение этого периода, у нас есть дата 20190805 (с номером = 1), поэтому среднее значение за последние 2 месяца будет 1/1=1.0.

Другой пример будет 20190930, последние 2 месяца будут начинаться с даты 20190730 до даты 20190929, у нас есть дата 20190805 (с номером = 1) и дата 20190909 (с номером = 0), поэтому среднее значение за последние 2 месяца будет (1+0)/2=0.5.

Как вычислить столбец avg_last_2m на основе столбцов date и number? Эффективность здесь очень важна, поскольку в реальности у меня будет около 100 тыс. Строк данных.

Это код для фрейма данных

test_data=pd.DataFrame({'date':['20190606','20190610','20190708','20190805','20190909','20190930'],'number':[3,5,4,1,0,0],\
                       'avg_last_2m':[None,3,4,4,1,0.5]})
.

Ответы [ 4 ]

3 голосов
/ 07 октября 2019

Проблема здесь - вычитание календарного месяца. Это не фиксированное окно (количество дней), поэтому переходить не просто (или возможно?).

Это может быть сделано с полным слиянием, но это быстро становится непрактичным, если ваш DataFrame длинный. С 100К строк мы получаем до 10Б строк DataFrame. Не невозможно, но определенно раздвигает границы. Вероятно, существуют более интеллектуальные алгоритмы, которые могут выполнять это слияние без объединения строк, что явно не произойдет в течение 2 месяцев.

Настройка

import pandas as pd
df = pd.DataFrame({'date':['20190606','20190610','20190708','20190805','20190909','20190930'],
                   'number':[3,5,4,1,0,0]})

df['date'] = pd.to_datetime(df.date, format='%Y%m%d')
#df = df.sort_values('date').reset_index(drop=True)  # Logic below requires sorting

Код

m = df.reset_index().assign(k=1)
m = m.merge(m, on='k').query('index_x > index_y')  # Full merge, no double count

# Only take average of observations within 2 months. 
m = m[m.date_x < (m.date_y + pd.offsets.DateOffset(months=2))].groupby('date_x').number_y.mean()

df['avg_last_2m'] = df.date.map(m)
#        date  number  avg_last_2m
#0 2019-06-06       3          NaN
#1 2019-06-10       5          3.0
#2 2019-07-08       4          4.0
#3 2019-08-05       1          4.0
#4 2019-09-09       0          1.0
#5 2019-09-30       0          0.5

Мы можем обменять память на время с очень медленным циклом. Вероятно, это займет ~ 10 минут.

def prev_2m(date, df):
    m = (df.date < date) & (df.date > (date - pd.offsets.DateOffset(months=2)))
    return df.loc[m, 'number'].mean()

df['avg_last_2m'] = df.date.apply(prev_2m, df=df)
0 голосов
/ 07 октября 2019

Ниже работает для меня.

df=pd.DataFrame({'date':['20190606','20190610','20190708','20190805','20190909','20190930'],'number':[3,5,4,1,0,0], 'avg_last_2m':[None,3,4,4,1,0.5]})

df["date"]=pd.to_datetime(df.date, format="%Y%m%d")
df["date_minus_2m"]=df["date"]-pd.DateOffset(months=2)

def avg_2m (row):
    avg_2m = df[(df.date>=row["date_minus_2m"])&(df.date<=row["date"])]["number"].mean()
    return avg_2m
df["avg_2m"]=df.apply(avg_2m, axis=1)
0 голосов
/ 07 октября 2019

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

import datetime
d = {'date':['20190606','20190610','20190708','20190805','20190909','20190930'],'number':[3,5,4,1,0,0]}
memory_dict = {}
memory_counter = {}
number_out = []
for date, number in reversed(list(zip(d['date'],d['number']))):
    dt = datetime.datetime.strptime(date, '%Y%m%d')
    for mem in list(memory_dict):
        if((mem-dt).days < 60):
            memory_dict[mem] += number
            memory_counter[mem] += 1
        else:
            number_out.append(memory_dict[mem]/memory_counter[mem])
            del memory_dict[mem]
    dt = dt - datetime.timedelta(days=1)
    memory_dict[dt] = 0
    memory_counter[dt] = 0
for mem in memory_dict:
    if (memory_counter[mem] != 0):
        number_out.append(memory_dict[mem]/memory_counter[mem])
    else:
        number_out.append(-1)
number_out.reverse()
print(number_out)

Я изначально думал, что это может работать со списком, но я не мог придумать, как это сделать. Проблема как-то меня заинтриговала, и мне пришлось ее попробовать.

0 голосов
/ 07 октября 2019

Это должно сработать

test_data=pd.DataFrame({'date':pd.to_datetime(['20190606','20190610','20190708','20190805','20190909','20190930']),'number':[3,5,4,1,0,0],\
                       'avg_last_2m':[None,3,4,4,1,0.5]})
offset =pd.offsets.DateOffset(months=2)

mean_k_months = test_data[test_data['date']>max(test_data['date'])-offset]['number'].mean() 

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

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