使用Python根据另一个数据框中的值有条件地更新数据框

时间:2020-10-23 16:21:29

标签: python pandas dataframe

想象一下2个数据框,其中MasterDB(df1)是中央数据库,而LatestResults(df2)是我每周检索的传入CSV。

df1 = pd.read_sv("Master.csv")
#with columns:
Index(['Timestamp', 'ID', 'Status', 'Username', 'URL', 'Last Seen'], 
      dtype='object')
Shape(1000,6)

df2 = pd.read_sv("LatestResults.csv") 
#with same columns:
Index(['Timestamp', 'ID', 'Status', 'Username', 'URL', 'Last Seen'], 
      dtype='object')
Shape(300,6)

基于某些条件,我想遍历df2中的(新)项目,并根据以下3个条件(假设目前只有3个条件)来处理项目:

a)如果不存在新项目(针对不匹配的“ URL”)

  • 附加到MasterDB(df1)
  • 设置'Status'=“ Net New”(在df1中)
  • 设置“最后一次见面” =今天。日期(在df1中)

b)如果已经存在新项目,请将其状态编辑为“仍然有效”(用于匹配“ URL”,而不考虑“ ID”)

  • 将“状态”设置为“仍处于活动状态”
  • 设置“最后一次见面” =今天。日期

c)如果在LatestResults中找不到MasterDB(df1)中的项目(仅用于匹配“ ID”和“ URL”)

  • 将“状态”设置为“已过期”

这是我到目前为止的代码:

def function(input1, input2):
    df1 = pd.read_csv(input1)
    df2 = pd.read_csv(input2)
    df1['Status'] = df1['Status'].astype(str)
    df1['Last Seen'] = df1['Last Seen'].astype(str)

    for i, row in df1.iterrows():
        tmp = df2.loc[(df2['URL'] == row['URL'])
        
        # A) STILL ACTIVE 
        if not tmp.empty:
            df1.at[i, 'Last Seen'] = str(datetime.now().replace(second=0, microsecond=0))
            df1.at[i, 'Status'] = "STILL ACTIVE"

    for i, row in df1.iterrows():
        tmp = df2.loc[(df2['URL'] == row['URL']) & (df2['ID'] == row['ID'])]

        # B) NOT FOUND // EXPIRED 
        if tmp.empty:
            df1.at[i, 'Status'] = "EXPIRED"

    for i, row in df2.iterrows():
        tmp = df1.loc[(df1['URL'] == row['URL']) & (df1['ID'] == row['ID'])]
        
        # C) NET NEW
        if tmp.empty:
            df1.at[i, 'Last Seen'] = str(datetime.now().replace(second=0, microsecond=0))
            row["Status"] = "NET NEW"
            df1 = df1.append(row, ignore_index=True)

    df1['Last Seen'].fillna("Not Set", inplace=True)
    df1.to_csv("MasterDB.csv", index=False)

该解决方案在大多数情况下都有效。追加了新项目,并且正确标记了(某些)先前存在的项目。我遇到的问题是:

  1. 匹配仍处于活动状态的记录时,如果未更改该项目(大多数情况下),则其值将覆盖为NaN。我该如何解决?
  2. 对于我的第三个案例“过期”,我想对和条件严苛,并且仅在同时满足两个条件的情况下才设置为过期。它似乎不起作用。这是思考的正确方法吗?

我已经探索过使用熊猫合并,联接和地图,但使用上面的方法坚持不懈。但是几天之后,我意识到它的体积很大。我很想听听有关如何进行不同设计的想法。预先非常感谢。

1 个答案:

答案 0 :(得分:0)

我将与您分享如何为您的问题实施解决方案。也许有不同的观点可以帮助您了解代码中的错误。

首先,我使用的数据。这是我的LatestResults.csv

   ID Status          Username        URL            Last Seen
0   1    new      Harry Potter     google  2020-10-20 12:00:00
1  20    new  Hermione Granger      yahoo  2020-10-20 12:00:00
2   3    new       Ron Weasley    twitter  2020-10-20 12:00:00
3   4    new            Hagrid  instagram  2020-10-20 12:00:00

这是我的MasterDB.csv

   ID  Status          Username       URL            Last Seen
0   1  blabla      Harry Potter    google  2020-10-20 12:00:00
1   2  blabla  Hermione Granger     yahoo  2020-10-20 12:00:00
2   3  blabla       Ron Weasley      bing  2020-10-20 12:00:00
3   4  blabla        McGonagall  facebook  2020-10-20 12:00:00
4   5  blabla        Dumbledore    google  2020-10-20 12:00:00
5   6  blabla      You-Know-Who     badoo  2020-10-20 12:00:00

我试图包括所有可能的组合:

  • Harry Potter在两个DataFrame中都具有相同的IDURL
  • Hermione Granger具有相同的URL,但不同的ID
  • Ron Weasley具有相同的ID,但不同的URL
  • McGonagall具有唯一的URL,但与ID相同的Hagrid
  • Dumbledore具有唯一的ID,但与URL相同的Harry Potter
  • You-Know-Who具有唯一性IDURL
  • Hagrid具有唯一的唯一URL,但它不在您的数据库中

我知道代码中的任何地方都没有使用username,但是这些名称有助于我们了解发生了什么。

现在输入代码。

def new_function(db_file, input_file):
    today = datetime.now().replace(second=0, microsecond=0)
    df_input = pd.read_csv(input_file)
    df_database = pd.read_csv(db_file)
    df_database['Last Seen'].astype(str)

    # Filter for input data
    net_new_filter = ~ df_input['URL'].isin(df_database['URL'])

    # Filter for DB data
    still_active = df_database['URL'].isin(df_input['URL'])
    expired_filter = (pd.merge(df_database, df_input, on=['ID', 'URL'],
                           how='left', indicator=True)['_merge'] != 'both')
    expired_filter = expired_filter & (~ still_active)

    # Applying filters
    df_database.loc[still_active, 'Status'] = 'Still Active'
    df_database.loc[still_active, 'Last Seen'] = str(today)
    df_database.loc[expired_filter, 'Status'] = 'Expired'

    new_data = df_input[net_new_filter].copy()
    new_data['Status'] = 'Net New'
    new_data['Last Seen'] = str(today)

    final_database = pd.concat([df_database, new_data],
                               ignore_index=True)

    final_database.to_csv("Test_Data/Output.csv", index=False)

我喜欢单独编写DataFrame过滤器,因为它可以使您更清楚地应用什么条件。

您可以使用方法.isin()来验证一列的元素是否位于另一列的某处。它直接检测“仍处于活动状态”和“净新状态”。

然后,如果要检查同一行的两列是否同时等于其他两列,则可以使用.merge()方法,例如描述的here。但是,我们一定不能覆盖“保持活动”条件,因此不能覆盖附加表达式expired_filter = expired_filter & (~ still_active)

运行代码后,将保存以下DataFrame:

   ID        Status          Username        URL            Last Seen
0   1  Still Active      Harry Potter     google  2020-10-23 20:18:00
1   2  Still Active  Hermione Granger      yahoo  2020-10-23 20:18:00
2   3       Expired       Ron Weasley       bing  2020-10-20 12:00:00
3   4       Expired        McGonagall   facebook  2020-10-20 12:00:00
4   5  Still Active        Dumbledore     google  2020-10-23 20:18:00
5   6       Expired      You-Know-Who      badoo  2020-10-20 12:00:00
6   3       Net New       Ron Weasley    twitter  2020-10-23 20:18:00
7   4       Net New            Hagrid  instagram  2020-10-23 20:18:00

具有新URL的行位于末尾,而来自原始数据库的与IDURL不完全匹配的行被设置为“过期”。最后,保留具有相应URL的行,并将它们的Status设置为“保持活动”。

希望此示例对您有所帮助。至少它要快一点,因为它不依赖于for循环。

如果数据没有帮助,请提供您自己的数据样本,以便我们可以使用相同的数据集进行测试。