【问题标题】:Remap values in pandas column with a dict, preserve NaNs使用 dict 重新映射 pandas 列中的值,保留 NaN
【发布时间】:2013-12-13 14:02:20
【问题描述】:

我有一本像这样的字典:di = {1: "A", 2: "B"}

我想将它应用到一个数据框的col1 列,类似于:

     col1   col2
0       w      a
1       1      2
2       2    NaN

得到:

     col1   col2
0       w      a
1       A      2
2       B    NaN

我怎样才能最好地做到这一点?出于某种原因,与此相关的谷歌搜索术语仅向我显示有关如何从 dicts 制作列的链接,反之亦然:-/

【问题讨论】:

    标签: python dictionary pandas remap


    【解决方案1】:

    您可以使用.replace。例如:

    >>> df = pd.DataFrame({'col2': {0: 'a', 1: 2, 2: np.nan}, 'col1': {0: 'w', 1: 1, 2: 2}})
    >>> di = {1: "A", 2: "B"}
    >>> df
      col1 col2
    0    w    a
    1    1    2
    2    2  NaN
    >>> df.replace({"col1": di})
      col1 col2
    0    w    a
    1    A    2
    2    B  NaN
    

    或直接在Series,即df["col1"].replace(di, inplace=True)

    【讨论】:

    • 如果col```` is tuple. The error info is 无法比较类型'ndarray(dtype=object)'和'tuple'```,它对我不起作用
    • 这似乎不再起作用了根本,鉴于答案来自 4 年前,这并不奇怪。鉴于操作的普遍性,这个问题需要一个新的答案......
    • @PrestonH 它非常适合我。正在运行:'3.6.1 |Anaconda custom (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]'
    • 它对我有用。但是如果我想替换所有列中的值怎么办?
    • 在显示的答案中,唯一对我有用的方法是直接替换系列。谢谢!
    【解决方案2】:

    map 可以比 replace 快得多

    如果您的字典有多个键,使用map 可能比replace 快得多。此方法有两个版本,具体取决于您的字典是否详尽地映射了所有可能的值(以及您是否希望不匹配项保留其值或转换为 NaN):

    详尽的映射

    在这种情况下,表格很简单:

    df['col1'].map(di)       # note: if the dictionary does not exhaustively map all
                             # entries then non-matched entries are changed to NaNs
    

    虽然map 最常使用函数作为参数,但它也可以使用字典或序列:Documentation for Pandas.series.map

    非详尽映射

    如果您有一个非详尽的映射并希望保留现有变量以用于不匹配,您可以添加fillna

    df['col1'].map(di).fillna(df['col1'])
    

    正如@jpp 在这里的回答:Replace values in a pandas series via dictionary efficiently

    基准测试

    在 pandas 0.23.1 版中使用以下数据:

    di = {1: "A", 2: "B", 3: "C", 4: "D", 5: "E", 6: "F", 7: "G", 8: "H" }
    df = pd.DataFrame({ 'col1': np.random.choice( range(1,9), 100000 ) })
    

    并使用%timeit 进行测试,似乎mapreplace 快大约10 倍。

    请注意,map 的加速会因您的数据而异。最大的加速似乎是使用大型字典和详尽的替换。有关更广泛的基准和讨论,请参阅@jpp 答案(上面链接)。

    【讨论】:

    • 这个答案的最后一段代码肯定不是最优雅的,但这个答案值得称赞。对于大型字典来说,它的速度要快几个数量级,并且不会用完我的所有 RAM。它使用字典重新映射了一个 10,000 行的文件,该字典在半分钟内有大约 900 万个条目。 df.replace 函数虽然对小型 dicts 很整洁有用,但在运行 20 分钟左右后就崩溃了。
    • map 也适用于我无法用replace 找到方法的索引
    • @AlexSB 我不能给出一个完全笼统的答案,但我认为 map 会更快并完成(我认为)同样的事情。一般来说,合并会比做同样事情的其他选项慢。
    • .map 当然是更好的方法。 .map 在有数百万条目的表上运行只需几秒钟,而.replace 运行了一个多小时。 .map 是推荐的方式!
    【解决方案3】:

    你的问题有点含糊。至少有三种两种解释:

    1. di 中的键引用索引值
    2. di 中的键引用df['col1']
    3. di 中的键是指索引位置(不是 OP 的问题,而是为了好玩。)

    以下是每种情况的解决方案。


    案例 1: 如果di 的键是指索引值,那么您可以使用update 方法:

    df['col1'].update(pd.Series(di))
    

    例如,

    import pandas as pd
    import numpy as np
    
    df = pd.DataFrame({'col1':['w', 10, 20],
                       'col2': ['a', 30, np.nan]},
                      index=[1,2,0])
    #   col1 col2
    # 1    w    a
    # 2   10   30
    # 0   20  NaN
    
    di = {0: "A", 2: "B"}
    
    # The value at the 0-index is mapped to 'A', the value at the 2-index is mapped to 'B'
    df['col1'].update(pd.Series(di))
    print(df)
    

    产量

      col1 col2
    1    w    a
    2    B   30
    0    A  NaN
    

    我已经修改了您原始帖子中的值,以便更清楚 update 在做什么。 注意di 中的键是如何与索引值相关联的。索引值的顺序——即索引locations——无关紧要。


    案例 2: 如果di 中的键引用df['col1'] 值,那么@DanAllan 和@DSM 将展示如何使用replace 实现这一点:

    import pandas as pd
    import numpy as np
    
    df = pd.DataFrame({'col1':['w', 10, 20],
                       'col2': ['a', 30, np.nan]},
                      index=[1,2,0])
    print(df)
    #   col1 col2
    # 1    w    a
    # 2   10   30
    # 0   20  NaN
    
    di = {10: "A", 20: "B"}
    
    # The values 10 and 20 are replaced by 'A' and 'B'
    df['col1'].replace(di, inplace=True)
    print(df)
    

    产量

      col1 col2
    1    w    a
    2    A   30
    0    B  NaN
    

    注意在这种情况下di 中的键是如何更改为匹配df['col1'] 中的


    案例 3: 如果di 中的键是指索引位置,那么您可以使用

    df['col1'].put(di.keys(), di.values())
    

    因为

    df = pd.DataFrame({'col1':['w', 10, 20],
                       'col2': ['a', 30, np.nan]},
                      index=[1,2,0])
    di = {0: "A", 2: "B"}
    
    # The values at the 0 and 2 index locations are replaced by 'A' and 'B'
    df['col1'].put(di.keys(), di.values())
    print(df)
    

    产量

      col1 col2
    1    A    a
    2   10   30
    0    B  NaN
    

    在这里,第一行和第三行被改变了,因为di 中的键是02,使用 Python 从 0 开始的索引指的是第一和第三位置。

    【讨论】:

    • replace 也同样出色,也许更适合这里发生的事情。
    • OP 发布的目标数据框是否消除了歧义?不过,这个答案很有用,所以+1。
    • @DSM:哎呀,你是对的,没有 Case3 的可能性,但我不认为 OP 的目标数据框将 Case1 与 Case2 区分开来,因为索引值等于列值。
    • 像其他许多人一样,@DSM 的方法很遗憾对我不起作用,但@unutbu 的案例 1 确实有效。 update()replace() 相比似乎有点笨拙,但至少它有效。
    【解决方案4】:

    DSM 有公认的答案,但编码似乎并不适合所有人。这是适用于当前版本的 pandas(截至 8/2018 为 0.23.4)的版本:

    import pandas as pd
    
    df = pd.DataFrame({'col1': [1, 2, 2, 3, 1],
                'col2': ['negative', 'positive', 'neutral', 'neutral', 'positive']})
    
    conversion_dict = {'negative': -1, 'neutral': 0, 'positive': 1}
    df['converted_column'] = df['col2'].replace(conversion_dict)
    
    print(df.head())
    

    你会看到它看起来像:

       col1      col2  converted_column
    0     1  negative                -1
    1     2  positive                 1
    2     2   neutral                 0
    3     3   neutral                 0
    4     1  positive                 1
    

    pandas.DataFrame.replace are here 的文档。

    【讨论】:

    • 我从来没有遇到过让 DSM 的答案运行的问题,而且我猜考虑到大多数其他人也没有获得高票数。您可能希望更具体地了解您遇到的问题。也许它与您的样本数据有关,与 DSM 的不同?
    • 嗯,可能是版本问题。不过,现在这两个答案都在这里了。
    • 接受答案中的解决方案仅适用于某些类型,Series.map() 似乎更灵活。
    【解决方案5】:

    鉴于map 比替换更快(@JohnE 的解决方案),您需要小心使用非详尽映射,您打算将特定值映射到 NaN。在这种情况下,正确的方法要求您在 .fillnamask 系列,否则您撤消到 NaN 的映射。

    import pandas as pd
    import numpy as np
    
    d = {'m': 'Male', 'f': 'Female', 'missing': np.NaN}
    df = pd.DataFrame({'gender': ['m', 'f', 'missing', 'Male', 'U']})
    

    keep_nan = [k for k,v in d.items() if pd.isnull(v)]
    s = df['gender']
    
    df['mapped'] = s.map(d).fillna(s.mask(s.isin(keep_nan)))
    

        gender  mapped
    0        m    Male
    1        f  Female
    2  missing     NaN
    3     Male    Male
    4        U       U
    

    【讨论】:

      【解决方案6】:

      如果您在数据数据框中有多个要重新映射的列,请添加到此问题:

      def remap(data,dict_labels):
          """
          This function take in a dictionnary of labels : dict_labels 
          and replace the values (previously labelencode) into the string.
      
          ex: dict_labels = {{'col1':{1:'A',2:'B'}}
      
          """
          for field,values in dict_labels.items():
              print("I am remapping %s"%field)
              data.replace({field:values},inplace=True)
          print("DONE")
      
          return data
      

      希望它对某人有用。

      干杯

      【讨论】:

      【解决方案7】:

      或者apply:

      df['col1'].apply(lambda x: {1: "A", 2: "B"}.get(x,x))
      

      演示:

      >>> df['col1']=df['col1'].apply(lambda x: {1: "A", 2: "B"}.get(x,x))
      >>> df
        col1 col2
      0    w    a
      1    1    2
      2    2  NaN
      >>> 
      

      【讨论】:

      • 当您的di 字典是列表字典时会发生什么?怎样才能只映射列表中的一个值?
      • 你可以,虽然我不明白你为什么会这样做。
      【解决方案8】:

      一个很好的完整的解决方案,保留你的类标签的地图:

      labels = features['col1'].unique()
      labels_dict = dict(zip(labels, range(len(labels))))
      features = features.replace({"col1": labels_dict})
      

      这样,您可以随时从labels_dict 中引用原始类标签。

      【讨论】:

        【解决方案9】:

        作为 Nico Coallier(应用于多个列)和 U10-Forward(使用方法的应用风格)提出的扩展,并将其总结为我建议的单行:

        df.loc[:,['col1','col2']].transform(lambda x: x.map(lambda x: {1: "A", 2: "B"}.get(x,x))
        

        .transform() 将每一列作为一个系列处理。与 .apply() 相反,它传递聚合在 DataFrame 中的列。

        因此,您可以应用 Series 方法 map()

        最后,感谢 U10,我发现了这种行为,您可以在 .get() 表达式中使用整个系列。除非我误解了它的行为并且它按顺序而不是按位处理系列。
        .get(x,x)accounts 表示您在映射字典中未提及的值,否则 .map() 方法会将其视为 Nan

        【讨论】:

        • .transform() 将每一列作为一个系列处理。与 .apply() 相反,它传递聚合在 DataFrame 中的列。 我刚刚尝试过,apply() 工作正常。也没有必要使用loc,这似乎过于复杂。 df[["col1", "col2"]].apply(lambda col: col.map(lambda elem: my_dict.get(elem, elem))) 应该可以正常工作。 .get(x,x)accounts 表示您在映射字典中未提及的值,否则 .map() 方法会将其视为 Nan 您也可以在之后使用 fillna()
        • 最后,感谢 U10,我发现了这种行为,您可以在 .get() 表达式中使用整个系列。除非我误解了它的行为并且它按顺序而不是按位处理系列。我无法重现这一点,你能详细说明一下吗?同名的变量可能在这里发挥了一些作用。
        【解决方案10】:

        更原生的 pandas 方法是应用如下替换函数:

        def multiple_replace(dict, text):
          # Create a regular expression  from the dictionary keys
          regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys())))
        
          # For each match, look-up corresponding value in dictionary
          return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text) 
        

        定义函数后,您可以将其应用到数据框。

        di = {1: "A", 2: "B"}
        df['col1'] = df.apply(lambda row: multiple_replace(di, row['col1']), axis=1)
        

        【讨论】:

        • 更原生的 pandas 方法是应用如下替换函数与 Pandas 提供的更简单的方法相比,这如何更“原生”(惯用的?)?跨度>
        【解决方案11】:

        您可以使用数据框中缺少的对来更新映射字典。例如:

        df = pd.DataFrame({'col1': ['a', 'b', 'c', 'd', np.nan]})
        map_ = {'a': 'A', 'b': 'B', 'd': np.nan}
        
        # Get mapping from df
        uniques = df['col1'].unique()
        map_new = dict(zip(uniques, uniques))
        # {'a': 'a', 'b': 'b', 'c': 'c', 'd': 'd', nan: nan}
        
        # Update mapping
        map_new.update(map_)
        # {'a': 'A', 'b': 'B', 'c': 'c', 'd': nan, nan: nan}
        
        df['col2'] = df['col1'].map(dct_map_new)
        

        结果:

          col1 col2
        0    a    A
        1    b    B
        2    c    c
        3    d  NaN
        4  NaN  NaN
        

        【讨论】:

          猜你喜欢
          • 2022-12-11
          • 2016-06-08
          • 1970-01-01
          相关资源
          最近更新 更多