在pandas中获取日期分位数

10

我有一些数据,看起来像这样:

user  timestamp  value1 
   a 2007-01-01       7 
   a 2007-02-02       8 
   a 2007-02-03       9 
   b 2007-02-04       1 
   a 2007-02-05       2 
   b 2007-02-06       3 
   b 2007-02-07       4 
   a 2007-02-08       5 
...

每个用户的条目数量不同。

我的目标是了解这些条目的生成速度,并输出类似以下内容的信息:

     last_entry median_entry first_entry
user                                    
a    2007-02-08   2007-02-03  2007-01-01
b    2007-02-07   2007-02-06  2007-02-04

到目前为止,我的代码如下:

gb = df.groupby('user')
time_median = gb['timestamp'].median()

但是这给了我一个DataError: No numeric types to aggregate错误,可能是因为日期不是数字。

我想把日期转换成时间戳,找到它们的中位数,然后再把它们转换成日期时间对象。这是最好的方法吗?


对于分位数,这应该没有关系,对吧? - Jeremy
啊,是的,请忽略那个,因为它与主题无关。我不确定当时我在想什么。 - mgilbert
6个回答

2

可能我的问题表述不够清晰,但我已经找到了适合我的解决方案。

def get_quantile(df, q):
    # Function that gets quantile from integer timestamp, then changes
    # back to a date_time object
    return pd.to_datetime(df['timestamp'].quantile(q, interpolation='nearest'))

df = pd.DataFrame(data={'user': np.random.choice(['a', 'b','c'], size=100, replace=True), 'value': np.random.random(size=100), 'date_time': pd.date_range(start=date(2016, 1,1), freq='D', periods=100)})

# Make a column of integer timestamps
df['timestamp'] = df['date_time'].astype('int')

editors = d.groupby('editor')

result = pd.DataFrame()
# Add columns of quantiles
result['first_quantile'] = get_quantile(editors, .25)
etc.

1
如果你不需要精确的中位数,可以对日期进行排序并取近似的中间值(例如,偶数个元素的中位数将是元组对中的第一个数字,因此1, 2, 2, 4, 4, 6的中位数将是2,因为(2,4)是中间元素)。
>>> df.groupby('user').timestamp.agg({
        'first_entry': 'first', 
        'last_entry': 'last', 
        'median_entry': lambda group: sorted(group)[len(group) // 2]})

      last_entry first_entry median_entry
user                                     
a     2007-02-08  2007-01-01   2007-02-03
b     2007-02-07  2007-02-04   2007-02-06

1
你可以使用.searchsorted()来查找每个用户最大值和最小值之间的一半天数:
df = pd.DataFrame(data={'user': np.random.choice(['a', 'b','c'], size=100, replace=True), 'value': np.random.random(size=100), 'time_stamp': pd.date_range(start=date(2016, 1,1), freq='D', periods=100)})

df.groupby('user')['time_stamp'].describe()

user        
a     count                      28
      unique                     28
      top       2016-02-03 00:00:00
      freq                        1
      first     2016-01-01 00:00:00
      last      2016-04-05 00:00:00
b     count                      38
      unique                     38
      top       2016-03-24 00:00:00
      freq                        1
      first     2016-01-02 00:00:00
      last      2016-04-08 00:00:00
c     count                      34
      unique                     34
      top       2016-01-28 00:00:00
      freq                        1
      first     2016-01-03 00:00:00
      last      2016-04-09 00:00:00

对于中位数:
df.groupby('user')['time_stamp'].apply(lambda x: x.sort_values().iloc[x.searchsorted(x.min() + (x.max()-x.min())/2)])

dtype: object
user    
a     54   2016-02-24
b     50   2016-02-20
c     51   2016-02-21
dtype: datetime64[ns]

1
假设您想将每个用户的初始日期之后的每个日期视为自初始日期以来的天数,则可以执行以下操作:
import pandas as pd
dts =  pd.date_range(start="2015-01-15", periods=20)
users = ["a","b"]*10
df = pd.DataFrame({"user":users, "timestamp":dts})

date_info = df.groupby("user").agg({"timestamp":[min, max]})
date_info.columns = date_info.columns.droplevel()

since_incept = lambda x: x - x.min()
df["days"] = df.groupby("user").transform(since_incept)
df["days"] = df["days"].dt.days

median_td = lambda x: pd.Timedelta(pd.Series.median(x), "D")
med = df.groupby("user").agg({"days":[median_td]})

date_info["median"] = date_info["min"] + med.loc[:, ("days", "<lambda>")]

1

不确定这是否完全符合您的要求,但您可以尝试使用 pd.TimeGrouper 并更改频率('20D''M'等),以适应您的时间范围。 这是一个使用5分位数(100天,20天组)的示例:

样本数据:

df = pd.DataFrame({'user': np.random.choice(['a', 'b','c'], size=100, replace=True),
                   'value': np.random.randint(10, size=100),
                   'time_stamp': pd.date_range(start=pd.datetime(2016, 1,1), freq='D', periods=100)})
df.head()

  time_stamp user  value
0 2016-01-01    b      3
1 2016-01-02    c      4
2 2016-01-03    a      8
3 2016-01-04    b      5
4 2016-01-05    c      5    

分位数生成:
quantiles = df.set_index('time_stamp').groupby([pd.TimeGrouper(freq='20D'), 'user'])['value'].sum()

time_stamp  user
2016-01-01  a       48
            b       22
            c       29
2016-01-21  a       28
            b       26
            c       25
2016-02-10  a       20
            b       57
            c       26
2016-03-01  a       25
            b       37
            c       35
2016-03-21  a       15
            b       37
            c       22

累积视图:

cum_quantiles = quantiles.groupby(level=[1]).cumsum()

time_stamp  user
2016-01-01  a        48
            b        22
            c        29
2016-01-21  a        76
            b        48
            c        54
2016-02-10  a        96
            b       105
            c        80
2016-03-01  a       121
            b       142
            c       115
2016-03-21  a       136
            b       179
            c       137

如果您想以百分比形式查看数值,请尝试添加一个百分比列:

totals = df.groupby('user')['value'].sum()
df['pct'] = df.apply(lambda x: x['value']/float(totals[x['user']]), axis=1)

重复上述步骤,将'value'更改为'pct'


1

使用我们自己的中位数函数进行分组

设置您的列

df['first_entry'] = df['timestamp']
df['median_entry'] = df['timestamp']
df['last_entry'] = df['timestamp']

定义我们自己的时间中位数函数,保留HTML标签,不做解释。
def median_time(x):
    x = list(x)
    median_entry = (len(x) - 1) / 2.0
    x.sort()
    if median_entry % 1 == 0:
        return x[int(median_entry)]
    else:
        lower_date = x[int(median_entry)]
        upper_date = x[int(median_entry) + 1]
        return lower_date + (upper_date - lower_date) / 2.0

设置聚合配置

agg_config = {'first_entry': pd.np.min,
              'median_entry': median_time,
              'last_entry': pd.np.max}

聚合

df.groupby('user').agg(agg_config)

结果

     last_entry median_entry first_entry
user                                    
a    2007-02-08   2007-02-03  2007-01-01
b    2007-02-07   2007-02-06  2007-02-04

替代方案,更简单的中位数

如果您只想使用完整日期进行中位数计算,也可以使用以下方法:

def median_time(x):
    x = list(x)
    median_entry = (len(x) - 1) / 2.0
    x.sort()
    return x[round(median_entry)]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接