Python 替换dataframe列中的值(如果其他';更好';价值存在于别处

Python 替换dataframe列中的值(如果其他';更好';价值存在于别处,python,python-2.7,pandas,dataframe,Python,Python 2.7,Pandas,Dataframe,我有一个大致如下结构的数据框架(它是一个事件参与者列表;池足够小,我们可以假设重复值指的是同一个人): 其目的是用一个国家名称替换“未知”值,若此人在另一年的参与中有一个已知的国家 在极不可能发生的情况下,他们在不同的年份被列在不同的国家,我很乐意将他们列在最接近“未知”年份的年份中的任何一个国家(因此,我们将在上面为John将“未知”改为“德国”) 我是一个完全的熊猫(和蟒蛇!)新手。我已经使用drop_副本创建了一个独特的名称/国家对列表,但我认为必须有一种更优雅的方法来完成其余的工作,而不

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

其目的是用一个国家名称替换“未知”值,若此人在另一年的参与中有一个已知的国家

在极不可能发生的情况下,他们在不同的年份被列在不同的国家,我很乐意将他们列在最接近“未知”年份的年份中的任何一个国家(因此,我们将在上面为John将“未知”改为“德国”)

我是一个完全的熊猫(和蟒蛇!)新手。我已经使用drop_副本创建了一个独特的名称/国家对列表,但我认为必须有一种更优雅的方法来完成其余的工作,而不是我目前深陷其中的混乱的列表、元组和dict转换。

一个非矢量化的解决方案是可能的。这只是一个薄薄的环。我们每行循环一次。如果国家未知,我们:

  • 筛选
    国家/地区
    不等于“未知”且
    名称
    等于行名称
  • 计算此子集的每一年与行年之间的绝对差值
  • 检索
    国家
    以获取最小绝对年份差异
下面是一个完整的示例:

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的使用,例如通过排序。如果性能是一个问题,你应该考虑另一种算法。

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

  • 用np.NaN替换
    Unknown
  • 对数据框进行排序,并将索引设置为“年”
  • 我们将创建一个字典,将“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

谢谢,这比我的干净多了!但我似乎无法应用它,因为df将作为未解决的参数出现,并且apply只能接受一个参数?我在进行数据刮取之后创建df,所以目前无法在main之前声明它。我应该把它放到一个单独的脚本中,还是有其他解决方案?@banbourg,对不起,我不知道你说的“df即将解决”是什么意思。这个答案假设您有一个问题中的数据帧。如果您的真实数据框不同,那么您可以尝试更新您的问题,我可能会提供帮助。对不起,我理解这可能是非常基本的-我的意思是,数据框是在main()中创建和填充的,当前(从web摘要中)。因此,当我将您提供的函数声明添加到脚本文件的顶部时,我得到一个NameError(“未定义全局名称'df',u'出现在索引0处”)。我只需要事先声明它为空变量吗?或者在单独的脚本中运行get_country,我可以在main?banbourg之前从csv加载df,我不知道。你可能需要问一个单独的问题。数据帧必须存储在变量中。我称之为
df
,你的可以称之为
my_dataframe
。只需相应地更改名称即可。这似乎更像是一个如何构造代码而不是操纵数据的问题。问题解决了:我不知道您可以在python中的其他函数中定义函数(因此,在这里,一旦df变量存在,我可以在main()中定义get_country()。哦!再次感谢你的帮助。
     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