计算组内移动中位数

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 天内 啊,是的,你是对的。我们正在以不同的方式阅读问题。如果询问是过去四天的中位数,无论这些日期是否存在于数据中,这都不会提供您正在寻找的结果。

以上是关于计算组内移动中位数的主要内容,如果未能解决你的问题,请参考以下文章

T-SQL 中的移动中位数、众数

CF Gym-101911K Medians and Partition

小白月赛22 G : 仓库地址

java中位运算 12>>33 ,应该怎么计算?过程是啥?

2607. 使子数组元素和相等

选第k小元素:特定分治策略