Вычисление движущейся медианы внутри группы

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

Вот примерные данные:

id      date        price
1637027 2020-01-21  7045204.0
280955  2020-01-11  3590000.0
782078  2020-01-28  2600000.0
1921717 2020-02-17  5500000.0
1280579 2020-01-23  869000.0
2113506 2020-01-23  628869.0
580638  2020-01-25  650000.0
1843598 2020-02-29  969000.0
2300960 2020-01-24  5401530.0
1921380 2020-02-19  1220000.0
853202  2020-02-02  2990000.0
1024595 2020-01-27  3300000.0
565202  2020-01-25  3540000.0
703824  2020-01-18  3990000.0
426016  2020-01-26  830000.0

Я был близок к объединению rolling и groupby:

df.groupby('date').rolling(window = 4, on = 'date')['price'].median()

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

Результат теперь выглядит так:

date        date      
2020-01-10  2020-01-10          NaN
            2020-01-10          NaN
            2020-01-10          NaN
            2020-01-10    3070000.0
            2020-01-10    4890000.0
                            ...    
2020-03-11  2020-03-11    4290000.0
            2020-03-11    3745000.0
            2020-03-11    3149500.0
            2020-03-11    3149500.0
            2020-03-11    3149500.0
Name: price, Length: 389716, dtype: float64

Кажется, он просто удалил 3 первых значения, а затем просто напечатал значение цены.

Можно ли получить одно запаздывающее/движущееся медианное значение за одну дату?


person Musisak    schedule 25.03.2020    source источник
comment
df.rolling(window = 4, on = 'date')['price'].median() ?   -  person Ezer K    schedule 26.03.2020
comment
кажется, просто удаляет первые 3 строки, а затем печатает цену за индекс, а не за день   -  person Musisak    schedule 26.03.2020
comment
о, теперь я понимаю, каждая дата имеет более 1 строки, дайте мне посмотреть...   -  person Ezer K    schedule 26.03.2020
comment
не нашел способа, кроме итерации, вот: pd.DataFrame([[x, df[(df['date']‹=x)&(df['date']›=x-pd.Timedelta(' 4d'))]['price'].median()] для x в df['date']], columns=['date','4d_median']).drop_duplicates()   -  person Ezer K    schedule 26.03.2020


Ответы (2)


Вы можете использовать rolling с окном частоты 5 дней, чтобы получить сегодня и последние 4 дня, затем drop_duplicates, чтобы сохранить последнюю строку за день. Сначала создайте copy (если вы хотите сохранить исходный), sort_values на дату и убедитесь, что столбец даты - это дата и время.

#sort and change to datetime
df_f = df[['date','price']].copy().sort_values('date')
df_f['date'] = pd.to_datetime(df_f['date'])

#create the column rolling
df_f['price'] = df_f.rolling('5D', on='date')['price'].median()

#drop_duplicates and keep the last row per day
df_f = df_f.drop_duplicates(['date'], keep='last').reset_index(drop=True)

print (df_f)

         date      price
0  2020-01-11  3590000.0
1  2020-01-18  3990000.0
2  2020-01-21  5517602.0
3  2020-01-23   869000.0
4  2020-01-24  3135265.0
5  2020-01-25  2204500.0
6  2020-01-26   849500.0
7  2020-01-27   869000.0
8  2020-01-28  2950000.0
9  2020-02-02  2990000.0
10 2020-02-17  5500000.0
11 2020-02-19  3360000.0
12 2020-02-29   969000.0
person Ben.T    schedule 26.03.2020
comment
Пытаюсь сделать то же самое с другим столбцом age, и мне нужно отфильтровать значения перед прокруткой. Я пробовал как: df_f['medAge'] = df_f[df_f['age'] >= 35].rolling('5D', on='date')['age'].median(), но иногда это дает правильный результат, а иногда NaN, хотя значения есть. Есть идеи, почему? - person Musisak; 26.03.2020
comment
@Musisak это из-за выравнивания индекса. скажем, df_f как 10 строк в целом, но из-за фильтра df_f[df_f['age'] >= 35] составляет всего 6 строк, затем после rolling это все еще 6 строк, но вы назначаете эти 6 значений в df_f, который имеет 10 строк, отсутствующий индекс заполняется NaN . Проверьте, есть ли Nan только там, где возраст меньше 35 лет! - person Ben.T; 26.03.2020

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

import pandas as pd
import statistics as stat
import numpy as np

# Replace with you data import
df = pd.read_csv('random_dates_prices.csv')

# Convert your date to a datetime
df['date'] = pd.to_datetime(df['date'])

# Sort your data by date
df = df.sort_values(by = ['date'])

# Create group by object
dates = df.groupby('date')

# Reformat dataframe for one row per day, with prices in a nested list
df = pd.DataFrame(dates['price'].apply(lambda s: s.tolist()))

# Extract price lists to a separate list
prices = df['price'].tolist()

# Initialize list to store past four days of prices for current day
four_days = []

# Loop over the prices list to combine the last four days to a single list
for i in range(3, len(prices), 1):
    x = i - 1
    y = i - 2
    z = i - 3
    four_days.append(prices[i] + prices[x] + prices[y] + prices[z])

# Initialize a list to store median values
medians = []

# Loop through four_days list and calculate the median of the last for days for the current date
for i in range(len(four_days)):
    medians.append(stat.median(four_days[i]))

# Create dummy zero values to add lists create to dataframe    
four_days.insert(0, 0)
four_days.insert(0, 0)
four_days.insert(0, 0)
medians.insert(0, 0)
medians.insert(0, 0)
medians.insert(0, 0)

# Add both new lists to data frames
df['last_four_day_prices'] = four_days
df['last_four_days_median'] = medians

# Replace dummy zeros with np.nan
df[['last_four_day_prices', 'last_four_days_median']] = df[['last_four_day_prices', 'last_four_days_median']].replace(0, np.nan)

# Clean data frame so you only have a single date a median value for past four days
df_clean = df.drop(['price', 'last_four_day_prices'], axis=1)
person Michael Longstreth    schedule 26.03.2020
comment
Это интересно, но это не удастся, если у вас нет всех дат. Потому что, когда вы делаете prices[i] + prices[x] + prices[y] + prices[z], вы получаете цены за дни, не входящие в правильный временной диапазон. - person Ben.T; 26.03.2020
comment
Я не верю, что это потерпит неудачу, если не все даты будут включены. В данных, которые я сгенерировал, не было всех дат, и это работает. Поскольку мы сортируем кадр данных по дате, любые даты в данных будут упорядочены. Затем, когда информация о ценах извлекается в список, у нас больше нет дат, только прайс-листы дат. Поэтому, когда мы перебираем цены, мы вычитаем из списка индекс, который не соответствует одному дню. - person Michael Longstreth; 26.03.2020
comment
Код работает, но я сомневаюсь в результате. Попробуйте сами с данными OP: на дату 2020-02-17 есть только одно значение 5500000, а ближайшая дата до 2020-02-02 так далеко от 4 дней до этого. поэтому медиана за последние 4 дня составляет 5500000, но с помощью вашего метода я получаю 3145000,0, потому что он использует значение из предыдущей даты, доступной в списке, даже если они не в течение 4 дней - person Ben.T; 26.03.2020
comment
Ах, да, вы правы. Мы читаем вопрос по-разному. Если аск является медианой за последние четыре дня, независимо от того, существуют ли эти даты в данных, это не даст нужного вам результата. - person Michael Longstreth; 26.03.2020