【问题标题】:changing a cell value based on another cells value in a dataframe根据数据框中的另一个单元格值更改单元格值
【发布时间】:2018-01-16 16:33:54
【问题描述】:

我正在尝试确定某列连续多少天高于或低于某个阈值。

>>> df.head()
            Open   High    Low  Close  Volume
Date
2004-08-19  49.96  51.98  47.93  50.12     NaN
2004-08-20  50.69  54.49  50.20  54.10     NaN
2004-08-23  55.32  56.68  54.47  54.65     NaN
2004-08-24  55.56  55.74  51.73  52.38     NaN
2004-08-25  52.43  53.95  51.89  52.95     NaN
>>>

对于上面的示例,我希望另一列 df['RDA'] 在列 Open 超过 50 的每一连续天递增。对于连续低于 50 的每一天,我想要第二列 df[ 'RDB'] 递增和 df['RDA'] 重置为 0。我尝试过 if/then 逻辑,但它不喜欢那样并给我一个值错误:

ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all(). how can i sort it out

我希望我的数据框的输出如下所示:

>>> df.head()
            Open   High    Low  Close  Volume    RDA   RDB
Date
2004-08-19  51.96  51.98  47.93  50.12     NaN    1      0
2004-08-20  50.69  54.49  50.20  54.10     NaN    2      0
2004-08-23  55.32  56.68  54.47  54.65     NaN    3      0
2004-08-24  45.56  55.74  51.73  52.38     NaN    0      1
2004-08-25  42.43  53.95  51.89  52.95     NaN    0      2
2004-08-26  41.96  51.98  47.93  50.12     NaN    0      3
2004-08-27  40.69  54.49  50.20  54.10     NaN    0      4
2004-08-28  55.32  56.68  54.47  54.65     NaN    1      0
2004-08-29  55.56  55.74  51.73  52.38     NaN    2      0
2004-08-30  52.43  53.95  51.89  52.95     NaN    3      0
>>>

Pandas 可以做到这一点吗?我知道您可以计算一列中的值,但到目前为止我一直无法找到具有连续值的方法。带有 2 个变量的 if/then 语句会起作用,但就像我上面提到的,当我尝试这样做时会出现值错误。任何帮助将不胜感激。

【问题讨论】:

    标签: python python-3.x pandas


    【解决方案1】:
    • 我将使用np.sign 来区分Open50。小于50 时为-1,恰好50 时为0,大于50 时为1
    • 接下来我将使用np.diff 来识别它何时从一个值切换到另一个
    • 然后我将使用cumsum 来定义连续符号组
    • 接下来我将使用cumcount 获取组内的计数
    • 最后我将使用np.where 来拆分cumcounts

    o = df.Open.values - 50
    signs = np.sign(o)
    changes = np.append(False, signs[:-1] != signs[1:])
    g = changes.cumsum()
    cumcounts = df.groupby(g).cumcount() + 1
    
    a = np.where(signs == 1,  cumcounts, 0)
    b = np.where(signs == -1, cumcounts, 0)
    
    df.assign(RDA=a, RDB=b)
    
                 Open   High    Low  Close  Volume  RDA  RDB
    Date                                                    
    2004-08-19  51.96  51.98  47.93  50.12     NaN    1    0
    2004-08-20  50.69  54.49  50.20  54.10     NaN    2    0
    2004-08-23  55.32  56.68  54.47  54.65     NaN    3    0
    2004-08-24  45.56  55.74  51.73  52.38     NaN    0    1
    2004-08-25  42.43  53.95  51.89  52.95     NaN    0    2
    2004-08-26  41.96  51.98  47.93  50.12     NaN    0    3
    2004-08-27  40.69  54.49  50.20  54.10     NaN    0    4
    2004-08-28  55.32  56.68  54.47  54.65     NaN    1    0
    2004-08-29  55.56  55.74  51.73  52.38     NaN    2    0
    2004-08-30  52.43  53.95  51.89  52.95     NaN    3    0
    

    【讨论】:

    • 你能解释一下吗?
    • @CoryMadden 我想着想快点造飞机就回答了这个问题。我什至不知道它是否有效。我会尽量添加一些解释。现在可能有点晚了。
    • @Alexander 我会在可以使用笔记本电脑时对其进行编辑。谢谢提醒
    • 绝对有趣。感谢您添加解释。
    • @Alexander 现在可以使用了。我必须在 cumcount 上加一个。
    【解决方案2】:

    这也可以使用 Python 提供的functools.reduce 方法来完成。首先创建一个目标数据的可迭代对象,因此在您的情况下:

    target = df.Open > 50
    

    这将是您稍后传递给functools.reduce 以“减少”的内容。 Reduce 本质上是map,但在列表元素之间保留一个值。这可以用来做你所要求的。

    我将尝试分解您可以使用的功能(完整显示在帖子末尾)。

    functools.reduce 允许您访问两个参数。您的累计值,以及您所在的当前列表项。它还允许您传入自己的初始化程序(查看任何内容之前的第一项)。有了这个我们可以遍历我们的列表,如果是True,由我们上面的目标系列确定,我们可以将列表中的最后一个元素加1,否则将0加到我们的累加器中。

    这需要一些技巧,将初始化程序设置为一个列表,其中包含值0,如[0],以便在第一次通过时,它可以获取“最后一个”元素并在没有的情况下使用它搞错了。

    一旦完成,列表前面会出现那个散乱的0,您可以使用切片[1:] 删除它,只取第二个元素及以后。

    您的RDB 列完全相同,只是您要确保它不是目标列表中的True,这只需要在条件语句中添加not

    完整的代码如下所示:

    import functools
    
    # Create a boolean series of your Open column
    target = df.Open > 50
    
    # For every item in your boolean series add a 1 to the previous value if it's over 50, otherwise reset
    df['RDA'] = functools.reduce(lambda x, y: x + ([x[-1] + 1] if y else [0]), target, [0])[1:]
    # Repeat, but for every `False` value in the series
    df['RDB'] = functools.reduce(lambda x, y: x + ([x[-1] + 1] if not y else [0]), target, [0])[1:]
    
    >>> df.head()
                Open    High    Low Close   Volume  RDA RDB
    Date                            
    2004-08-19  49.96   51.98   47.93   50.12   NaN 0   1
    2004-08-20  50.69   54.49   50.20   54.10   NaN 1   0
    2004-08-23  55.32   56.68   54.47   54.65   NaN 2   0
    2004-08-24  55.56   55.74   51.73   52.38   NaN 3   0
    2004-08-25  52.43   53.95   51.89   52.95   NaN 4   0
    

    【讨论】:

      【解决方案3】:

      首先,在数据框中添加一个标志列以指示开盘价是否高于目标价格 50(真或假)。

      然后,您可以使用compare-cumsum-groupby pattern 来识别此标志的累积分组,并将cumsum 应用于每个此类组。

      我们现在需要反转标志,使 1 为 0,0 为 1,然后使用相同的策略计算 rdb

      最后,我们删除flag 列(我使用.iloc[:, :-1] 删除它,因为我将它添加为最后一列)并附加新的RDARDB 列。

      target_price = 50
      df = df.assign(flag=df.Open.gt(target_price))  # True if `Open` greater than `target_price`, otherwise False.
      
      rda = df.groupby((df['flag'] != df['flag'].shift()).cumsum()).flag.cumsum()
      df['flag'] = ~df['flag']  # Invert flag for RDB.
      rdb = df.groupby((df['flag'] != df['flag'].shift()).cumsum()).flag.cumsum()
      
      df = df.iloc[:, :-1].assign(RDA=rda, RDB=rdb)
      >>> df
            Date   Open   High    Low  Close  Volume  RDA  RDB
      0  8/19/04  51.96  51.98  47.93  50.12     NaN    1    0
      1  8/20/04  50.69  54.49  50.20  54.10     NaN    2    0
      2  8/23/04  55.32  56.68  54.47  54.65     NaN    3    0
      3  8/24/04  45.56  55.74  51.73  52.38     NaN    0    1
      4  8/25/04  42.43  53.95  51.89  52.95     NaN    0    2
      5  8/26/04  41.96  51.98  47.93  50.12     NaN    0    3
      6  8/27/04  40.69  54.49  50.20  54.10     NaN    0    4
      7  8/28/04  55.32  56.68  54.47  54.65     NaN    1    0
      8  8/29/04  55.56  55.74  51.73  52.38     NaN    2    0
      9  8/30/04  52.43  53.95  51.89  52.95     NaN    3    0
      

      【讨论】:

        【解决方案4】:

        我最初误解了,没有意识到您想在计数发生变化时重新开始计数。我认为最好的方法是在这样的行中使用DataFrame.apply

        In [226]: def increment(row):
             ...:     global rda
             ...:     global rdb
             ...:     if row.Open > 50:
             ...:         row.RDA = int(next(rda))
             ...:         rdb = count()
             ...:     else:
             ...:         row.RDB = next(rdb)
             ...:         rda = int(count())
             ...:     return row
        In [227]: df['RDA'] = 0
        In [228]: df['RDB'] = 0
        In [229]: df.apply(increment, axis=1)
                     Open   High    Low  Close  Volume  RDA  RDB
        Date                                                    
        2004-08-19  49.96  51.98  47.93  50.12     NaN  0.0  1.0
        2004-08-20  50.69  54.49  50.20  54.10     NaN  0.0  0.0
        2004-08-23  55.32  56.68  54.47  54.65     NaN  1.0  0.0
        2004-08-24  55.56  55.74  51.73  52.38     NaN  2.0  0.0
        2004-08-25  52.43  53.95  51.89  52.95     NaN  3.0  0.0
        

        我不知道为什么它们在列中显示为浮点数,我猜 pandas 认为这就是你想要的。数据最初来自count 作为int。而且我通常不是全局变量的粉丝,但是当 DataFrame.apply 位于 increment 函数之外时,它们无法访问这些变量。

        【讨论】:

          【解决方案5】:
          def fun(d):
              j = 0
              flag = 2
              rda = []
              rdb = []
              for i in range(d.shape[0]):
                  if d.loc[i,'Open'] >= 50:
                      if flag == 1:
                          j = 0
                      j = j + 1
                      rda.append(j)
                      rdb.append(0)
                      flag = 0
                  else:
                      if flag == 0:
                          j=0
                      j = j + 1
                      rdb.append(j)
                      rda.append(0)
                      flag = 1
          
              return rda,rdb
          
          df['RDA'],df['RDB'] = fun(df)
          

          【讨论】: