【问题标题】:Aggregate interval data by binning to create time series通过分箱聚合间隔数据以创建时间序列
【发布时间】:2021-10-25 20:44:07
【问题描述】:

我有一个 DataFrame,其中包含有关开始时间、结束时间、用户、产品和使用率的记录。用户能够以不同的速率多次使用相同的产品。我想将此 DataFrame 转换为时间序列数据集,其中为每个用户创建 4 小时的 bin,其中将使用率相加。由于开始时间和结束时间可能与 bin 边界重叠,因此需要计算该 bin 上的小数使用量,并且每个产品都有自己的列。

原始数据框:

start end user product usage_rate
1 12 A X_1 10
8 15 A X_1 20
3 7 B X_1 3
3 8 B X_2 70

期望的输出:

user bin start end X_1 X_2
A 0-4 0 4 30 0
A 4-8 4 8 40 0
A 8-12 8 12 120 0
A 12-16 12 16 60 0
B 0-4 0 4 3 70
B 4-8 4 8 9 280

我尝试使用 pd.cut 创建 bin,但只能创建开始时间匹配和/或结束时间匹配的 DataFrame,而不是两者之间的 bin。我想避免使用 for 循环,因为当将其扩展到 10k 用户和 20 个产品时,这些循环会变得非常慢。我首先想到的过程将每一行复制所需的时间箱数(第一条记录为 0-4、4-8、8-12),计算该箱内的持续时间,将持续时间乘以 usage_rate 得到 @ 987654325@ 值,然后是 groupby(['user', 'bin']) 并检索这些 used_in_bin 值的总和。然后最后一步是旋转产品名称和值以获取表格。

困难的部分是我无法为时间范围(日期时间或数字)创建时间箱。在我的范围的开始时间和结束时间之间,我可以通过什么方式为丢失的 bin 创建重复记录?

【问题讨论】:

    标签: python pandas dataframe


    【解决方案1】:

    您拥有的数据由数学阶跃函数描述,staircase 已为此目的构建在 pandas 和 numpy 之上。

    设置

    df = pd.DataFrame(
        {
            "start":[1,8,3,3],
            "end":[12,15,7,8],
            "user":["A", "A", "B", "B"],
            "product":["X_1", "X_1", "X_1", "X_2"],
            "usage_rate":[10,20,3,70]
        }
    )
    

    解决方案

    我们将为每个用户和产品创建一个阶梯函数。阶梯函数由staircase.Stairs 类表示。这个类是 staircase 就像 Seriespandas。为此,我们根据这些变量对数据帧进行分组,并将子数据帧传递给Stairs constructor

    import staircase as sc
    
    stepfunctions = df.groupby(["user", "product"]).apply(sc.Stairs, "start", "end", "usage_rate")
    

    我们的stepfunctions 变量如下所示。它是一个系列,具有多索引,值为Stairs 对象。

    user  product
    A     X_1        <staircase.Stairs, id=2516839332104>
    B     X_1        <staircase.Stairs, id=2516834889160>
          X_2        <staircase.Stairs, id=2516835627464>
    dtype: object
    

    您可以使用staircase 中的步进函数做很多事情,包括绘图。

    stepfunctions["A", "X_1"].plot(style="hlines")
    

    您想要做的是create bins and integrate(找到下面的区域)这些步进函数。对于阶跃函数sf,这意味着以下计算

    sf.slice([0,4,8,12,16]).integral()
    

    我们可以使用pandas.Series.apply 对我们所有的步进函数执行此操作。

    binned = stepfunctions.apply(lambda sf: sf.slice([0,4,8,12,16]).integral())
    

    binned 变量将是一个数据框,与stepfunctions 具有相同的索引,每个 bin 间隔有一列

                   [0, 4)  [4, 8)  [8, 12)  [12, 16)
    user product                                   
    A    X_1        30.0    40.0    120.0      60.0
    B    X_1         3.0     9.0      0.0       0.0
         X_2        70.0   280.0      0.0       0.0
    

    要在tidy format 中获取此数据,可以使用以下方法

    tidy_result = binned.melt(ignore_index=False).rename({"variable":"bin"}).reset_index()
    

    tidy_result 数据框将如下所示:

       user product       bin  value
    0     A     X_1    [0, 4)   30.0
    1     B     X_1    [0, 4)    3.0
    2     B     X_2    [0, 4)   70.0
    3     A     X_1    [4, 8)   40.0
    4     B     X_1    [4, 8)    9.0
    5     B     X_2    [4, 8)  280.0
    6     A     X_1   [8, 12)  120.0
    7     B     X_1   [8, 12)    0.0
    8     B     X_2   [8, 12)    0.0
    9     A     X_1  [12, 16)   60.0
    10    B     X_1  [12, 16)    0.0
    11    B     X_2  [12, 16)    0.0
    

    这对于您的目的可能已经足够了。如果您想以您提交的确切格式获取它,那么这应该很容易:

    • 使用tidy_result.pivot(index=["user", "bin"], columns="product") 将产品列转换为每个产品的列。用 0 填充 AX_2NA 值。
    • 使用tidy_result["start"] = pd.IntervalIndex(tidy_result["bin"]).left

    回顾一下,解决方案(导入后)归结为以下三行

    stepfunctions = df.groupby(["user", "product"]).apply(sc.Stairs, "start", "end", "usage_rate")
    binned = stepfunctions.apply(lambda sf: sf.slice([0,4,8,12,16]).integral())
    tidy_result = binned.melt(ignore_index=False).rename({"variable":"bin"}).reset_index()
    

    【讨论】:

      猜你喜欢
      • 2021-07-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-28
      • 2020-12-24
      • 2021-12-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多