【问题标题】:Replace value in dataframe column, if other 'better' value exists elsewhere如果其他地方存在其他“更好”的值,则替换数据框列中的值
【发布时间】:2018-06-16 17:20:35
【问题描述】:

我有一个大致如下结构的数据框(它是一个事件参与者列表;池足够小,我们可以假设重复值指的是同一个人):

id_1 id_2 id_3 ... year  name   country
1_c  2_a  3_a      2011  John   France
1_b  2_a  3_c      2010  Jill   UK
1_c  2_b  3_c      2018  John   Germany
1_c  2_b  3_c      2014  Jason  Italy
1_c  2_b  3_b      2017  John   Unknown

目的是用国家名称替换“未知”值,如果此人在另一年的参与中拥有已知国家/地区。

万一它们在不同年份被列在不同国家/地区下,我很乐意将它们标记在最接近“未知”年份的年份中被列在哪个国家/地区下(以上,我们会将 John 的 'Unknown' 更改为 'Germany')。

我是一个完整的 pandas(和 python!)新手。我已经使用 drop_duplicates 创建了一个唯一名称/国家/地区对的列表,但我假设必须有一种更优雅的方式来完成剩下的工作,而不是我目前深陷其中的列表、元组和字典转换的混乱。

【问题讨论】:

    标签: python python-2.7 pandas dataframe


    【解决方案1】:

    一个非矢量化解决方案可以通过pd.DataFrame.apply 实现。这只是一个隐蔽的循环。我们循环浏览每一行。如果国家未知,我们:

    • 过滤country 不等于“未知”且name 等于行名称。
    • 计算每一年与该子集的行年之间的绝对差。
    • 检索country 以获取最小绝对年差。

    这是一个完整的例子:

    def get_country(row):
        if row['country'] != 'Unknown':
            return row['country']
        else:
            res = df.loc[(df['country'] != 'Unknown') & (df['name'] == row['name'])]\
                    .assign(year_diff=(df['year']-row['year']).abs())
            return res.loc[res['year_diff'].idxmin(), 'country'] if not res.empty else 'Unknown'
    
    df['country'] = df.apply(get_country, axis=1)
    
    print(df)
    
      id_1 id_2 id_3  year   name  country
    0  1_c  2_a  3_a  2011   John   France
    1  1_b  2_a  3_c  2010   Jill       UK
    2  1_c  2_b  3_c  2018   John  Germany
    3  1_c  2_b  3_c  2014  Jason    Italy
    4  1_c  2_b  3_b  2017   John  Germany
    

    肯定会有使用 Pandas / NumPy 进行优化的聪明方法,例如通过排序。如果性能是一个问题,您应该考虑另一种算法。

    【讨论】:

    • 谢谢,这比我以前的干净多了!我似乎无法应用它,因为 df 即将出现未解决并且 apply 只能采用一个参数?我在进行数据抓取后创建了 df,所以我目前无法在 main 之前声明它。我应该将其转储到单独的脚本中还是有其他解决方案?
    • @banbourg,抱歉,我不知道您所说的“df 出现未解决”是什么意思。该答案假设您在问题中有一个数据框。如果您的真实数据框不同,那么您可以尝试更新您的问题,我也许可以提供帮助。
    • 抱歉,我很欣赏这可能是超级基本的 - 我的意思是数据框是在 main() 中创建并填充的,目前(来自网络抓取)。因此,当我将您提供的函数声明添加到脚本文件的顶部时,我得到一个 NameError(“未定义全局名称'df'”,你'发生在索引 0')。我只需要事先将其声明为空变量吗?或者在单独的脚本中运行 get_country,我可以在 main 之前从 csv 加载 df?
    • @banbourg,我不知道。您可能需要提出一个单独的问题。您的数据框必须存储在变量中。我叫它df,你的可以叫my_dataframe。只需相应地更改名称即可。这似乎更像是一个如何构建代码而不是操作数据的问题。
    • 问题已解决:我没有意识到你可以在 python 中定义其他函数(所以在这里,一旦我的 df 变量存在,我可以在 main() 中定义 get_country() )。哦!再次感谢您的帮助。
    【解决方案2】:

    这是一种可能性。我认为由于应用程序的原因,它可能仍然很慢,但如果与行数相比,唯一的“名称”数量较少,它可能会更快:

    • 用 np.NaN 替换 Unknown
    • 对 DataFrame 进行排序并将索引设置为“年份”
    • 我们将创建一个字典,将 'country' 中的所有字符串映射到一个数值。
    • 这允许您随后使用pd.Series.interpolate(method='nearest')
      • 如果是第一个条目或最后一个条目,则需要抛出一些案例以正确填写,如果所有内容都是NaN,则需要避免错误
    • 插值后,将值映射回来。

    代码如下:

    import pandas as pd
    import numpy as np
    
    df = df.replace('Unknown', np.NaN)
    df = df.sort_values(['name', 'year']).set_index('year')
    
    dct = dict(zip(df.country[df.country.notnull()].unique(), 
                   range(df.country[df.country.notnull()].nunique())))
    inv_dct = {v: k for k, v in dct.items()}
    df['country'] = df['country'].map(dct)
    
    df['country'] = df.groupby('name')['country'].apply(
                       lambda x: x.interpolate(method='nearest').bfill().ffill()
                                 if x.notnull().sum() > 1 else x.bfill().ffill())
    
    df['country'] = df['country'].map(inv_dct)
    

    输出:

         id_1 id_2 id_3   name  country
    year                               
    2014  1_c  2_b  3_c  Jason    Italy
    2010  1_b  2_a  3_c   Jill       UK
    2011  1_c  2_a  3_a   John   France
    2017  1_c  2_b  3_b   John  Germany
    2018  1_c  2_b  3_c   John  Germany
    

    【讨论】:

      猜你喜欢
      • 2016-08-18
      • 2014-02-06
      • 1970-01-01
      • 1970-01-01
      • 2014-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-20
      • 1970-01-01
      相关资源
      最近更新 更多