7

更新:从 0.20.0 版开始,pandas cut/qcut 确实处理日期字段。有关更多信息,请参阅新增功能。

pd.cut 和 pd.qcut 现在支持 datetime64 和 timedelta64 dtypes(GH14714、GH14798)

原始问题: Pandas cut 和 qcut 函数非常适合“存储”连续数据以用于数据透视表等,但我看不到一种简单的方法来混合使用日期时间轴。令人沮丧的是,熊猫在所有与时间相关的东西上都很棒!

这是一个简单的例子:

def randomDates(size, start=134e7, end=137e7):
    return np.array(np.random.randint(start, end, size), dtype='datetime64[s]')

df = pd.DataFrame({'ship' : randomDates(10), 'recd' : randomDates(10), 
                   'qty' : np.random.randint(0,10,10), 'price' : 100*np.random.random(10)})
df

     price      qty recd                ship
0    14.723510   3  2012-11-30 19:32:27 2013-03-08 23:10:12
1    53.535143   2  2012-07-25 14:26:45 2012-10-01 11:06:39
2    85.278743   7  2012-12-07 22:24:20 2013-02-26 10:23:20
3    35.940935   8  2013-04-18 13:49:43 2013-03-29 21:19:26
4    54.218896   8  2013-01-03 09:00:15 2012-08-08 12:50:41
5    61.404931   9  2013-02-10 19:36:54 2013-02-23 13:14:42
6    28.917693   1  2012-12-13 02:56:40 2012-09-08 21:14:45
7    88.440408   8  2013-04-04 22:54:55 2012-07-31 18:11:35
8    77.329931   7  2012-11-23 00:49:26 2012-12-09 19:27:40
9    46.540859   5  2013-03-13 11:37:59 2013-03-17 20:09:09

要按价格或数量分组,我可以使用 cut/qcut 对它们进行存储:

df.groupby([pd.cut(df['qty'], bins=[0,1,5,10]), pd.qcut(df['price'],q=3)]).count()

                       price  qty recd ship
qty     price               
(0, 1]  [14.724, 46.541]   1   1   1   1
(1, 5]  [14.724, 46.541]   2   2   2   2
        (46.541, 61.405]   1   1   1   1
(5, 10] [14.724, 46.541]   1   1   1   1
        (46.541, 61.405]   2   2   2   2
         (61.405, 88.44]   3   3   3   3

但是我看不到任何简单的方法可以用我的“recd”或“ship”日期字段做同样的事情。例如,生成一个类似的计数表,按(比如)每月的 recd 和 ship 桶细分。似乎 resample() 有所有的机制来分时段,但我不知道如何在这里应用它。'date cut' 中的桶(或级别)相当于 pandas.PeriodIndex,然后我想用它所属的周期标记 df['recd'] 的每个值?

所以我正在寻找的输出类型是这样的:

ship    recv     count
2011-01 2011-01  1
        2011-02  3
        ...      ...
2011-02 2011-01  2
        2011-02  6
...     ...      ...

更一般地说,我希望能够在输出中混合和匹配连续或分类变量。想象一下 df 还包含一个带有红色/黄色/绿色值的“状态”列,那么也许我想按状态、价格桶、船和 recd 桶总结计数,所以:

ship    recv     price   status count
2011-01 2011-01  [0-10)   green     1
                            red     4
                 [10-20) yellow     2
                  ...      ...    ...
        2011-02  [0-10)  yellow     3
        ...      ...       ...    ...

作为一个额外的问题,将上面的 groupby() 结果修改为仅包含一个名为“count”的输出列的最简单方法是什么?

4

4 回答 4

5

这是使用 pandas.PeriodIndex 的解决方案(警告:PeriodIndex 似乎不支持倍数 > 1 的时间规则,例如“4M”)。我认为您的奖金问题的答案是.size()

In [49]: df.groupby([pd.PeriodIndex(df.recd, freq='Q'),
   ....:             pd.PeriodIndex(df.ship, freq='Q'),
   ....:             pd.cut(df['qty'], bins=[0,5,10]),
   ....:             pd.qcut(df['price'],q=2),
   ....:            ]).size()
Out[49]: 
                qty      price 
2012Q2  2013Q1  (0, 5]   [2, 5]    1
2012Q3  2013Q1  (5, 10]  [2, 5]    1
2012Q4  2012Q3  (5, 10]  [2, 5]    1
        2013Q1  (0, 5]   [2, 5]    1
                (5, 10]  [2, 5]    1
2013Q1  2012Q3  (0, 5]   (5, 8]    1
        2013Q1  (5, 10]  (5, 8]    2
2013Q2  2012Q4  (0, 5]   (5, 8]    1
        2013Q2  (0, 5]   [2, 5]    1
于 2013-05-02T03:51:35.753 回答
4

只需要设置你想重采样的字段的索引,这里有一些例子

In [36]: df.set_index('recd').resample('1M',how='sum')
Out[36]: 
                 price  qty
recd                       
2012-07-31   64.151194    9
2012-08-31   93.476665    7
2012-09-30   94.193027    7
2012-10-31         NaN  NaN
2012-11-30         NaN  NaN
2012-12-31   12.353405    6
2013-01-31         NaN  NaN
2013-02-28  129.586697    7
2013-03-31         NaN  NaN
2013-04-30         NaN  NaN
2013-05-31  211.979583   13

In [37]: df.set_index('recd').resample('1M',how='count')
Out[37]: 
2012-07-31  price    1
            qty      1
            ship     1
2012-08-31  price    1
            qty      1
            ship     1
2012-09-30  price    2
            qty      2
            ship     2
2012-10-31  price    0
            qty      0
            ship     0
2012-11-30  price    0
            qty      0
            ship     0
2012-12-31  price    1
            qty      1
            ship     1
2013-01-31  price    0
            qty      0
            ship     0
2013-02-28  price    2
            qty      2
            ship     2
2013-03-31  price    0
            qty      0
            ship     0
2013-04-30  price    0
            qty      0
            ship     0
2013-05-31  price    3
            qty      3
            ship     3
dtype: int64
于 2013-05-01T13:43:01.543 回答
1

我想出了一个依赖于 datetime64[ns] 的底层存储格式的想法。如果你像这样定义 dcut()

def dcut(dts, freq='d', right=True):
    hi = pd.Period(dts.max(), freq=freq) + 1   # get first period past end of data
    periods = pd.PeriodIndex(start=dts.min(), end=hi, freq=freq)
    # get a list of integer bin boundaries representing ns-since-epoch
    # note the extra period gives us the extra right-hand bin boundary we need
    bounds = np.array(periods.to_timestamp(how='start'), dtype='int')
    # bin our time field as integers
    cut = pd.cut(np.array(dts, dtype='int'), bins=bounds, right=right)
    # relabel the bins using the periods, omitting the extra one at the end
    cut.levels = periods[:-1].format()
    return cut

然后我们可以做我想做的事:

df.groupby([dcut(df.recd, freq='m', right=False),dcut(df.ship, freq='m', right=False)]).count()

要得到:

                price qty recd ship
2012-07 2012-10   1    1    1    1
2012-11 2012-12   1    1    1    1
        2013-03   1    1    1    1  
2012-12 2012-09   1    1    1    1
        2013-02   1    1    1    1  
2013-01 2012-08   1    1    1    1
2013-02 2013-02   1    1    1    1
2013-03 2013-03   1    1    1    1
2013-04 2012-07   1    1    1    1
        2013-03   1    1    1    1  

我想您可以类似地定义 dqcut() ,它首先将每个日期时间值“四舍五入”为表示其包含周期开始的整数(以您指定的频率),然后使用 qcut() 在这些边界中进行选择。或者首先对原始整数值执行 qcut() 并根据您选择的频率对结果进行四舍五入?

奖金问题还没有乐趣吗?:)

于 2013-05-01T17:58:46.510 回答
0

如何使用Series并将DataFrame您感兴趣的部分放入其中,然后调用cut系列对象?

price_series = pd.Series(df.price.tolist(), index=df.recd)

接着

 pd.qcut(price_series, q=3)

等等。(虽然我认为@Jeff 的回答是最好的)

于 2013-05-01T13:43:17.957 回答