【问题标题】:How to know if a record has been modified or included new in a pandas dataframe如何知道记录是否已被修改或包含在 pandas 数据框中的新记录
【发布时间】:2021-09-16 22:34:06
【问题描述】:

我有一个数据框,其中可以包含新行,但我必须知道进入数据框的新行是否是对某些现有记录的修改,或者相反,它是一条新记录。

例如,输入数据框:

A B Population Start End timestamp
A1 B1 100 2021-05-15 00:00:00 2021-06-30 00:00:00 2021-07-06 00:00:00
A1 B1 250 2021-05-30 00:00:00 2021-06-02 00:00:00 2021-06-06 00:00:00
A2 B3 350 2021-05-10 00:00:00 2021-05-12 00:00:00 2021-07-06 00:00:00
A2 B4 125 2021-06-02 00:00:00 2021-06-04 00:00:00 2021-07-06 00:00:00

我们可以看到第 1 行是对第 0 行的修改,注意时间戳更高并且除了日期之外,pop 值也被修改了。

预期输出:

A B Population Population_prev Start Start_prev End End_prev Type timestamp
A1 B1 100 250 2021-05-15 00:00:00 2021-05-30 00:00:00 2021-06-30 00:00:00 2021-06-02 00:00:00 Mod 2021-07-06 00:00:00
A2 B3 350 NaN 2021-05-10 00:00:00 NaN 2021-05-12 00:00:00 NaN New 2021-07-06 00:00:00
A2 B4 125 NaN 2021-06-02 00:00:00 NaN 2021-06-04 00:00:00 NaN New 2021-07-06 00:00:00

谢谢!

【问题讨论】:

  • 是否有允许唯一标识行的列? AB 也许?
  • 如何“包含”新行?还是问题所在?
  • @Cimbali 正确,A列是一般组,B列是A的子组。所以A + B只有一种组合
  • @Giacomo 这就是问题所在,如何获得预期输出。

标签: python pandas dataframe date datetime


【解决方案1】:

因此,如果您按时间戳排序,并在定义唯一行的列上使用groupby,您可以获得所需的所有信息。使用last 获取每组的最后一行,使用nth 获取倒数第二行:

>>> groups = df.sort_values('timestamp').groupby(['A', 'B'])
>>> groups.last()
         Population                 Start                   End            timestamp
A   B                                                                               
A1  B1          100  2021-05-15 00:00:00   2021-06-30 00:00:00   2021-07-06 00:00:00
A2  B3          350  2021-05-10 00:00:00   2021-05-12 00:00:00   2021-07-06 00:00:00
    B4          125  2021-06-02 00:00:00   2021-06-04 00:00:00   2021-07-06 00:00:00
>>> groups.nth(-2)
A1  B1          250  2021-05-30 00:00:00   2021-06-02 00:00:00   2021-06-06 00:00:00

现在所有这些数据帧都在列 AB 上建立索引,因此您可以简单地在 join 添加后缀,重置索引,然后就完成了:

>>> mod = groups.last().join(groups.nth(-2), rsuffix='_prev').reset_index()
>>> mod
     A    B  Population                 Start                   End            timestamp  Population_prev            Start_prev              End_prev       timestamp_prev
0  A1   B1          100  2021-05-15 00:00:00   2021-06-30 00:00:00   2021-07-06 00:00:00            250.0  2021-05-30 00:00:00   2021-06-02 00:00:00   2021-06-06 00:00:00
1  A2   B3          350  2021-05-10 00:00:00   2021-05-12 00:00:00   2021-07-06 00:00:00              NaN                   NaN                   NaN                  NaN
2  A2   B4          125  2021-06-02 00:00:00   2021-06-04 00:00:00   2021-07-06 00:00:00              NaN                   NaN                   NaN                  NaN

然后是一些细节,让它看起来像你所拥有的:

>>> col_order = [
...     *df.columns[:2],
...     *(new_col for col in df.columns[2:-1] for new_col in [col, f'{col}_prev']),
...     'type', 'timestamp'
... ]
>>> row_type = mod['timestamp_prev'].isna().map({True: 'New', False: 'Mod'})
>>> mod.join(row_type.rename('type')).reindex(col_order, axis='columns')
     A    B  Population  Population_prev                 Start            Start_prev                   End              End_prev type            timestamp
0  A1   B1          100            250.0  2021-05-15 00:00:00   2021-05-30 00:00:00   2021-06-30 00:00:00   2021-06-02 00:00:00   Mod  2021-07-06 00:00:00
1  A2   B3          350              NaN  2021-05-10 00:00:00                    NaN  2021-05-12 00:00:00                    NaN  New  2021-07-06 00:00:00
2  A2   B4          125              NaN  2021-06-02 00:00:00                    NaN  2021-06-04 00:00:00                    NaN  New  2021-07-06 00:00:00

另一种适用于任意数量重复值的技术是使用pivot。让我们使用相同的 groupby 但使用 cumcount() 来定义列的顺序:

>>> num = df.sort_values('timestamp').groupby(['A', 'B']).cumcount().rename('num')
>>> num
1    0
0    1
2    0
3    0
Name: num, dtype: int64
>>> pvt = df.join(num).pivot(index=['A', 'B'], columns='num', values=['Population', 'Start', 'End'])
>>> pvt
      Population                     Start                                       End                     
num            0    1                    0                    1                    0                    1
A  B                                                                                                     
A1 B1        250  100  2021-05-30 00:00:00  2021-05-15 00:00:00  2021-06-02 00:00:00  2021-06-30 00:00:00
A2 B3        350  NaN  2021-05-10 00:00:00                  NaN  2021-05-12 00:00:00                  NaN
   B4        125  NaN  2021-06-02 00:00:00                  NaN  2021-06-04 00:00:00                  NaN

如您所见,它可以满足您的需求,但在列中具有多索引。让我们将其展平为普通列,我们就完成了:

>>> pvt.columns = [f'{col}_prev{n if n > 1 else ""}' if n > 0 else col for col, n in pvt.columns]
>>> pvt.reset_index()
    A   B Population Population_prev                Start           Start_prev                  End             End_prev
0  A1  B1        250             100  2021-05-30 00:00:00  2021-05-15 00:00:00  2021-06-02 00:00:00  2021-06-30 00:00:00
1  A2  B3        350             NaN  2021-05-10 00:00:00                  NaN  2021-05-12 00:00:00                  NaN
2  A2  B4        125             NaN  2021-06-02 00:00:00                  NaN  2021-06-04 00:00:00                  NaN

【讨论】:

  • 一个问题:如果同一个组A2 + B4有两个新条目,我怎样才能让它两个都拿走?因为它只需要两者之一。例如,它们是两个不同的时间间隔,我想同时使用它们,因为它们将被视为两个新增内容。
  • @GBM 你需要很多列。但是你可以得到.nth(-3)等等直到groups.size().max()
  • 我添加了一种适用于任意数量列 @GBM 的方法,并且比在 .nth() 上循环更简单
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-12-26
  • 1970-01-01
  • 1970-01-01
  • 2013-11-30
  • 1970-01-01
  • 2016-09-20
  • 2012-10-10
相关资源
最近更新 更多