【问题标题】:R group_by() + rleid() equivalent in PythonPython 中的 R group_by() + rleid() 等价物
【发布时间】:2020-04-17 11:08:57
【问题描述】:

我在 Python 中有以下数据框:

df = pd.DataFrame.from_dict({'measurement_id': np.repeat([1, 2], [6, 6]),
                         'min': np.concatenate([np.repeat([1, 2, 3], [2, 2, 2]), 
                                                np.repeat([1, 2, 3], [2, 2, 2])]),
                         'obj': list('AB' * 6),
                         'var': [1, 2, 1, 2, 2, 1, 2, 1, 2, 1, 1, 1]})

首先,在object 定义的每个组中,我想将id 分配给measurement_idvar 列的唯一运行。如果这些列的任何值发生变化,它就会开始新的运行,应该分配新的 id。所以

df['rleid_output'] = [1, 1, 1, 1, 2, 2, 3, 3, 3, 3, 4, 3]

然后,对于rleid_output 定义的每个组,我想检查运行持续了多少分钟(min 列)给我expected_output 列:

df['expected_output'] = [2, 2, 2, 2, 1, 1, 2, 3, 2, 3, 1, 3]

如果是 R,我会按如下方式进行:

df <- data.frame(measurement_id = rep(1:2, each = 6),
           min = rep(rep(1:3, each = 2), 2),
           object = rep(LETTERS[1:2], 6),
           var = c(1, 2, 1, 2, 2, 1, 2, 1, 2, 1, 1, 1))
df %>% 
  group_by(object) %>% 
  mutate(rleid = data.table::rleid(measurement_id, var)) %>% 
  group_by(object, rleid) %>% 
  mutate(expected_output = last(min) - first(min) + 1) 

所以我需要的主要是 R data.table::rleid 等效项,它可以与 Python pd.DataFrame.groupby 子句一起使用。任何想法如何解决这个问题?

@Edit:新的、更新的数据框示例:

df = pd.DataFrame.from_dict({'measurement_id': np.repeat([1, 2], [6, 6]),
                         'min': np.concatenate([np.repeat([1, 2, 3], [2, 2, 2]), 
                                                np.repeat([1, 2, 3], [2, 2, 2])]),
                         'obj': list('AB' * 6),
                         'var': [1, 2, 2, 2, 1, 1, 2, 1, 2, 1, 1, 1]})
df['rleid_output'] = [1, 1, 2, 1, 3, 2, 4, 3, 4, 3, 5, 3]
df['expected_output'] = [1, 2, 1, 2, 1, 1, 2, 3, 2, 3, 1, 3]

【问题讨论】:

    标签: python r pandas run-length-encoding


    【解决方案1】:

    更新答案

    问题是measurement_id, obj, var每组中的min列应该保持顺序。我们可以在measurement_id, obj, var 上按组检查,然后检查min 列中的差异是否大于1。如果是这样,我们将其标记为 expected_output 中的唯一持续时间:

    df['grouper'] = (df.groupby(['measurement_id', 'obj', 'var'])['min']
                     .apply(lambda x: x.diff().fillna(1).eq(1))
                    )
    
    df['expected_output'] = (
        df.groupby(['measurement_id', 'obj', 'var'])['grouper'].transform('sum').astype(int)
    )
    
    df = df.drop(columns='grouper')
    
        measurement_id  min obj  var  expected_output
    0                1    1   A    1                1
    1                1    1   B    2                2
    2                1    2   A    2                1
    3                1    2   B    2                2
    4                1    3   A    1                1
    5                1    3   B    1                1
    6                2    1   A    2                2
    7                2    1   B    1                3
    8                2    2   A    2                2
    9                2    2   B    1                3
    10               2    3   A    1                1
    11               2    3   B    1                3
    

    旧答案,遵循 OP 的逻辑

    我们可以通过使用GroupBy.diff 来获取您的rleid_output 来实现这一点,基本上每次var 更改每个measurement_id& obj 时都有一个唯一标识符

    之后使用GroupBy.nunique来测量minutes的数量:

    rleid_output = df.groupby(['measurement_id', 'obj'])['var'].diff().abs().bfill()
    df['expected_output'] = (df.groupby(['measurement_id', 'obj', rleid_output])['min']
                             .transform('nunique'))
    
        measurement_id  min obj  var  expected_output
    0                1    1   A    1                2
    1                1    1   B    2                2
    2                1    2   A    1                2
    3                1    2   B    2                2
    4                1    3   A    2                1
    5                1    3   B    1                1
    6                2    1   A    2                2
    7                2    1   B    1                3
    8                2    2   A    2                2
    9                2    2   B    1                3
    10               2    3   A    1                1
    11               2    3   B    1                3
    

    【讨论】:

    • 原始解决方案中有两个陷阱(现在标记为旧答案) - 使用 bfill 填充 NaN 值和 nunique 用于计算分钟数。如果缺少min 的某些中间值,则后者将失败,如果var 的值在第二个min 发生变化,则前者将失败,例如'var': [1, 2, 2, 2, 2, 1, 2, 1, 2, 1, 1, 1]
    • 另外,如果一个对象(比如A)在第一分钟内的值var 等于1,然后在第二分钟内等于2,那么您更新的答案将失败,并在第 3 分钟再次等于 1
    • 您能否用我的答案不够用的新数据框更新您的答案,以便我进行相应调整。
    • 新示例 df 是否也会改变您的预期输出?
    【解决方案2】:

    为了模仿 R rleid 函数的行为,可以首先创建一个人工列,检查当前值与之前的值相比是否发生了变化。在这种情况下,我们应该在分组的var 系列上执行此操作:

    var_grpd = df.groupby(['measurement_id', 'obj'])['var']
    df['tmp'] = (var_grpd.shift(0) != var_grpd.shift(1))
    

    然后,我们可以使用这个人工的tmp 列来获得rleid_output2。之后,tmp 列就不再需要了。

    df['rleid_output2'] = df.groupby('obj')['tmp'].cumsum().astype(int)
    df.drop('tmp', axis = 1, inplace = True)
    

    最后,要检查var 值持续了多少分钟,我们可以计算组内最后一分钟和第一分钟之间的差异。

    df['expected_output2'] = df.groupby(['obj', 'rleid_output2'])['min'] \
                               .transform(lambda x: x.iat[-1] - x.iat[0] + 1)
    

    .iat 类似于.iloc,但允许我们访问DataFrameSeries 中的单个值。

    【讨论】:

      猜你喜欢
      • 2020-09-14
      • 2018-07-18
      • 2016-06-14
      • 2016-05-23
      • 1970-01-01
      • 2022-10-13
      • 1970-01-01
      • 2020-10-14
      • 2017-11-27
      相关资源
      最近更新 更多