计算组内移动中位数
Posted
技术标签:
【中文标题】计算组内移动中位数【英文标题】:Calculating moving median within group 【发布时间】:2020-07-06 12:08:51 【问题描述】:我想在 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
我已经接近结合滚动和分组:
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 个第一个值,然后只是打印了价格值。
是否有可能在每个日期获得一个滞后/移动中值?
【问题讨论】:
df.rolling(window = 4, on = 'date')['price'].median() ? 似乎只是删除前 3 行,然后打印每个索引的价格,而不是一天 哦,现在我明白了,每个日期都有不止 1 行,让我看看... 没有找到方法,但迭代,这里是:pd.DataFrame([[x, df[(df['date'] =x-pd.Timedelta('4d'))]['price'].median()] for x in df['date']], columns=['date','4d_median']).drop_duplicates() 【参考方案1】:您可以使用频率窗口为 5 天的 rolling
获取今天和最后 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
【讨论】:
尝试对另一列age
做同样的事情,我需要在滚动之前过滤这些值。我试过:df_f['medAge'] = df_f[df_f['age'] >= 35].rolling('5D', on='date')['age'].median()
,但它有时会产生正确的结果,有时会产生正确的结果NaN
,尽管有值。任何想法为什么?
@Musisak 这是因为索引对齐。假设df_f
总共有 10 行,但是由于过滤器,df_f[df_f['age'] >= 35]
只有 6 行,然后在 rolling
之后它仍然是 6 行,但是您在有 10 行的 df_f
中分配这 6 个值, 缺失的索引用NaN
填充。检查Nan
是否仅适用于年龄低于 35 岁的人!【参考方案2】:
这是一个循序渐进的过程。可能有更有效的方法来获得你想要的东西。请注意,如果您有日期的时间信息,则需要在按日期分组之前删除该信息。
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)
【讨论】:
这很有趣,但如果您没有所有日期,它会失败。因为当您执行prices[i] + prices[x] + prices[y] + prices[z]
时,您将获取不在正确时间范围内的几天的价格。
如果不包括所有日期,我认为它不会失败。我生成的数据没有所有日期,这很有效。由于我们按日期对数据框进行排序,因此数据中的任何日期都是有序的。然后当价格信息被提取到一个列表中时,我们不再有日期,只有日期价格列表。因此,当我们遍历价格时,我们从列表索引中减去,它不代表一天。
代码有效,但我对结果有些怀疑。使用 OP 数据自己尝试一下:对于日期 2020-02-17,只有一个值 5500000,并且距离 4 天前最近的日期是 2020-02-02。所以过去 4 天的中位数是 5500000 但使用你的方法,我得到 3145000.0 因为它使用列表中上一个可用日期的值,即使它们不在 4 天内
啊,是的,你是对的。我们正在以不同的方式阅读问题。如果询问是过去四天的中位数,无论这些日期是否存在于数据中,这都不会提供您正在寻找的结果。以上是关于计算组内移动中位数的主要内容,如果未能解决你的问题,请参考以下文章