用字典重新映射熊猫列中的值


317

我有一本字典,看起来像这样: 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

我怎样才能最好地做到这一点?由于某种原因,与此相关的谷歌搜索术语仅向我显示了有关如何根据字典创建列的链接,反之亦然:-/

Answers:


339

您可以使用.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)


1
当如果它不适合我的工作col```` is tuple. The error info is 不能比的类型“ndarray(D型=对象)”和“tuple'```
芜赵

18
看来这根本不起作用了鉴于答案来自4年前,这不足为奇。鉴于操作的一般性,此问题需要一个新的答案...
PrestonH

2
@PrestonH对我来说完美。跑步:'3.6.1 |Anaconda custom (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]'

这个对我有用。但是,如果要替换ALL列中的值,该怎么办?
famargar '18

2
对我显示的答案唯一有效的方法是直接替换系列。谢谢!
Dirigo

241

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最常用函数作为参数,但也可以选择字典或系列: Pandas.series.map的文档

非穷举映射

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

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

如@jpp的答案在这里: 通过字典有效地替换熊猫系列中的值

基准测试

在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,它的map速度大约比速度快10倍replace

请注意,您的加速map会随数据而变化。最大的提速似乎是使用大词典和详尽的替换方法。有关更广泛的基准测试和讨论,请参见@jpp答案(上面链接)。


17
此答案的最后一段代码当然不是最优雅的,但是此答案值得一提。对于大型词典来说,速度要快几个数量级,并且不会用完我所有的RAM。它使用字典重新映射了10,000行文件,该字典在半分钟内有大约900万个条目。该df.replace功能虽然整洁且对小命令有用,但在运行20分钟左右后便崩溃了。
griffinc


@griffinc感谢您的反馈,并指出,此后,我以一种更简单的方式(不是@jpp)来更新此答案(感谢@jpp)
JohnE '18

1
map也适用于无法找到解决方案的索引replace
Max Ghenis

1
@AlexSB我无法给出一个完全笼统的答案,但我认为map会更快并且完成(我认为)同一件事。通常,合并比做相同事情的其他选项要慢。
JohnE,

59

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

  1. 中的键di引用索引值
  2. 中的键是didf['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与索引值关联。索引值的顺序(即索引位置)无关紧要。


情况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

在这里,第一行和第三行被更改了,因为其中的键di02,使用Python基于0的索引对其进行索引,它们指向第一位置和第三位置。


replace同样好,也许对于这里发生的事情来说是一个更好的词。
Dan Allan 2013年

OP发布的目标数据框是否消除了歧义?尽管如此,这个答案还是有用的,所以+1。
DSM 2013年

@DSM:糟糕,您是对的,没有Case3的可能性,但是我不认为OP的目标数据帧将Case1与Case2区别开来,因为索引值等于列值。
unutbu

像其他许多发布者一样,@ DSM的方法对我不起作用,但是@unutbu的案例1起作用了。update()与相比replace(),似乎有点糊涂,但至少有效。
Geoff

4

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

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

希望它对某人有用。

干杯


1
该功能已经由提供DataFrame.replace(),尽管我不知道何时添加。
AMC

3

DSM已经接受了答案,但是编码似乎并不适合所有人。这是与当前版本的熊猫一起使用的版本(截至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的文档在这里


我从来没有遇到过让帝斯曼(DSM)得到答案的问题,而且我猜想鉴于大多数其他人也没有投票,因此获得了很高的票数。您可能想更详细地说明所遇到的问题。也许与您的示例数据(与DSM不同)有关?
JohnE '18年

嗯,也许是版本问题。不过,这两个答案都在这里。
wordforthewise

1
接受的答案中的解决方案仅适用于某些类型,Series.map()似乎更灵活。
AMC

2

或做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字典是列表字典时会发生什么?您如何只映射列表中的一个值?
FaCoffee

您可以,尽管我不知道为什么。
AMC

2

给定map的速度比替换(@JohnE的解决方案)要快,因此在打算将特定值映射到的非穷举映射时,您NaN需要格外小心。在这种情况下,正确的方法需要在您mask使用Series时执行.fillna,否则撤消到的映射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

1

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

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

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


1

作为对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()表达式中使用整个Series。除非我误解了它的行为,并且它按顺序而不是按位处理序列。您在映射字典中未提及的值
.get(x,x)帐户,否则该.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)账户的价值观,你没有在这将被否则被视为楠你的映射字典提.map()方法你也可以使用fillna()之后。
AMC

最后,由于U10,我发现了此行为,您可以在.get()表达式中使用整个Series。除非我误解了它的行为,并且它按顺序而不是按位处理序列。我无法复制此内容,您能详细说明一下吗?命名相同的变量可能在这里发挥了作用。
AMC

0

一种更本地的熊猫方法是应用如下替换函数:

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)

一种更本地化的熊猫方法是应用如下所示的替换函数:与熊猫提供的更为简单的方法相比,这种“原生”方法(惯用语言)是什么?
AMC
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.