使用不同列的条件匹配更新旧 Dataframe ,并在Pandas中添加新行

wydwbb8l  于 2023-02-27  发布在  其他
关注(0)|答案(1)|浏览(107)

我有一个旧的 Dataframe ,包含以下列和许多行,如下所示

>old_df
date/time    Name   detect_ID   category  ID
12/1/2023    XXX    1           B        1400
12/1/2023    XXY    1,3,7       B        1402
12/1/2023    XXY    4           A        1403
12/1/2023    XXY    4           B        1407
.....

我有一些关于new_df的信息,它有类似的列,基于此,我想更新old_df。新 Dataframe 为:

>new_df
date/time    Name   detect_ID   category  ID
13/1/2023    XXX    1           B        1400
14/1/2023    XXY    1,3,8       B        1402
14/1/2023    XXY    1           B        1405
.....

对于更新,我希望以下条件:
1.我想遍历old_df的行,同时检查new_df的每一行及其信息。但我只想检查 Category 列值为“B”的old_df的那些行。
1.首先,程序将记住第一行的 IDnew_df用于第一次迭代(并使用连续迭代遍历new_df的所有行)。在遍历old_df的行时。如果new_df的第一行的 IDold_df的任何 ID 都不匹配,则将从new_df中获取整行并将其添加为old_df的新行,同时在old_df中创建一个名为 Identify 的新列并分配一个值 new
如果此new_dfIDold_df的任何 ID 匹配,则它将遍历该特定行的 detect_ID 列值。
答:如果old_df的特定 detect_ID 值与第一行的new_dfdetect_ID 值匹配,它将采用new_df的特定行并替换匹配的old_df行,而新创建的列 identify 的值将为 updated。在这种情况下。此外,如您所见,detect_ID 有多个值:我想分别检查它们,其中一些数字可能是整数,所以基本上用**,将它们拆分,并将它们转换为整数。
B.如果
old_dfdetect_ID 值与第一行的new_dfdetect_ID 值不匹配,它将从new_df中取出整行,并将其添加为old_df的新行,同时进入列 identify 并分配值 new
1.对于
old_df的行,“ID”值与具有相同“ID”或相同“ID”但“detect_ID”值不匹配的new_df的任何行不匹配,将在old_df中保持不变,并在 identify 列中具有值 unchanged
我希望它迭代
old_df的所有行,直到new_df的每一行都在old_df**中更新。
对于给定的示例,我希望输出 Dataframe 如下所示:

>output
date/time    Name   detect_ID   category  ID   identify
13/1/2023    XXX    1           B        1400   updated  [Case A] 
14/1/2023    XXY    1           B        1402   updated  [Case A with multiple detect_ID]
14/1/2023    XXY    3           B        1402   updated
12/1/2023    XXY    7           B        1402   unchanged  [Step 3, Id matches but detect_id do not ]
14/1/2023    XXY    8           B        1402   new        [Case B]
12/1/2023    XXY    4           A        1403   unchanged   
12/1/2023    XXY    4           B        1407   unchanged [Step3 , id not found in new_df]

我正在使用下面的代码,但它似乎没有工作的方式,我想要的。它给了很多重复,并没有迭代通过很多行的旧_df太多。

old_df = pd.read_csv('old.csv')
new_df = pd.read_csv('new.csv')

# Create a set of tuples representing the unique (ID, Detector Id) pairs in the old dataframe
unique_pairs = set()
for _, row in old_df.iterrows():
    detector_ids = [int(x) for x in str(row['Detect_ID']).split(',')]
    for detector_id in detector_ids:
        unique_pairs.add((row['ID'], detect_id))

# Iterate over the rows in the new dataframe and check if their (ID, Detector Id) pair is in the set of unique pairs
new_rows = []
updated_rows = []
for _, row in new_df.iterrows():
    detector_ids = [int(x) for x in str(row['Detect_ID']).split(',')]
    for detector_id in detector_ids:
        if (row['ID'], detector_id) in unique_pairs:
            old_row = old_df.loc[(old_df['ID'] == row['ID']) & (old_df['Detect_ID'].str.contains(str(detector_id)))]
            if not old_row.empty:
                old_row = old_row.iloc[0]
                old_row['Date/Time'] = row['date/time']
                old_df.loc[(old_df['ID'] == row['ID']) & (old_df['Detector_ID'].str.contains(str(detector_id))), 'date/time'] = old_row['date/time']
                updated_rows.append(old_row)
        else:
            row['Identify'] = 'new'
            new_rows.append(row)
            unique_pairs.add((row['ID'], detector_id))

# Append the new rows to the old dataframe and write the updated dataframe to a new file
old_df = old_df.append(new_rows, ignore_index=True)
for row in updated_rows:
    row['Identify'] = 'updated'
old_df = old_df.append(updated_rows, ignore_index=True)
old_df.to_csv('updated.csv', index=False)
zengzsys

zengzsys1#

依赖于df.iterrows几乎总是意味着在pandas中的操作是次优的(例如,参见SO post)。

    • 步骤1**

使用Series.str.splitdf.explodedetect_ID列中类似1,3,7的条目放入单独的行中。同时应用于两个dfs。让我们使用Series.astypedetect_ID中所有值的类型更改为int(假设数据确实由数字字符组成)。
因为我们只想检查category列中包含B值的行,所以使用Series.eqnew_df中过滤掉任何非B值(尽管在当前示例中不存在这样的值)。

old_df['detect_ID'] = old_df['detect_ID'].str.split(',')
old_df = old_df.explode('detect_ID', ignore_index=False)
old_df['detect_ID'] = old_df['detect_ID'].astype(int)

new_df['detect_ID'] = new_df['detect_ID'].str.split(',')
new_df = new_df.explode('detect_ID', ignore_index=False)
new_df['detect_ID'] = new_df['detect_ID'].astype(int)
new_df = new_df[new_df['category'].eq('B')]

# `dfs` now as follows
old_df

   date/time Name detect_ID category    ID
0  12/1/2023  XXX         1        B  1400
1  12/1/2023  XXY         1        B  1402
1  12/1/2023  XXY         3        B  1402
1  12/1/2023  XXY         7        B  1402
2  12/1/2023  XXY         4        A  1403
3  12/1/2023  XXY         4        B  1407
    • 第二步**

应用df.merge。我们希望在['Name','detect_ID','category', 'ID']上合并,保留两边的所有条目(因此:how='outer'),还添加了一个indicator列(称为identify),它将告诉我们每行的源代码。添加自定义后缀(例如,'_old'代替默认的'_x')是为了清楚起见。

res = old_df.merge(new_df, on=['Name','detect_ID','category', 'ID'], 
                   how='outer', indicator='identify', suffixes=('_old','_new'))

res

  date/time_old Name detect_ID category    ID date/time_new    identify
0     12/1/2023  XXX         1        B  1400     13/1/2023        both
1     12/1/2023  XXY         1        B  1402     14/1/2023        both
2     12/1/2023  XXY         3        B  1402     14/1/2023        both
3     12/1/2023  XXY         7        B  1402           NaN   left_only
4     12/1/2023  XXY         4        A  1403           NaN   left_only
5     12/1/2023  XXY         4        B  1407           NaN   left_only
6           NaN  XXY         8        B  1402     14/1/2023  right_only
7           NaN  XXY         1        B  1405     14/1/2023  right_only
    • 步骤3**

在此阶段,我们要决定需要为列date/time保留哪个值。我们需要_new中所有行的值(1)存在于dfs中,并且由于left_only条目在列date/time_new中将具有NaN值,因此我们可以依赖于Series.where来实现这一点:

res['date/time'] = res['date/time_new'].where(res['date/time_new'].notna(),
                                              res['date/time_old'])
    • 步骤4**

以下工作尚待完成:

  • 更新列identify的值。我们可以使用Series.map来完成此操作。
  • 以正确的顺序从res中选择正确的列。让我们使用df_old中的列名加上df.loc中的identify,并为此链接df.sort_values。这里也使用df.reset_index
mapper = {'both': 'updated',
          'left_only': 'unchanged',
          'right_only': 'new'}

res['identify'] = res['identify'].map(mapper)
res = (res.loc[:, list(old_df.columns) + ['identify']]
       .sort_values(['ID', 'detect_ID'])
       .reset_index(drop=True))

res

   date/time Name  detect_ID category    ID   identify
0  13/1/2023  XXX          1        B  1400    updated
1  14/1/2023  XXY          1        B  1402    updated
2  14/1/2023  XXY          3        B  1402    updated
3  12/1/2023  XXY          7        B  1402  unchanged
4  14/1/2023  XXY          8        B  1402        new
5  12/1/2023  XXY          4        A  1403  unchanged
6  14/1/2023  XXY          1        B  1405        new
7  12/1/2023  XXY          4        B  1407  unchanged

注意:正如@Ashyam在上面的注解中提到的,您想要的结果没有ID为1405的行,它只存在于df_new中。我在这里假设您 * 确实 * 希望在新的df中包含此条目。如果不希望,您可以如下所示将其删除:

res = res[res['ID'].isin(old_df['ID'])].reset_index(drop=True)

当然,该操作实际上可以已经应用于new_df,参见上面的列category中的值B的滤波器。

相关问题