Переопределить значения в столбце панд с помощью dict


318

У меня есть словарь, который выглядит так: 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

Как я могу лучше всего это сделать? По некоторым причинам термины поиска в Google, относящиеся к этому, показывают только ссылки о том, как сделать столбцы из разногласий и наоборот: - /

Ответы:


342

Вы можете использовать .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 (dtype = object)' и 'tuple'```
Zhao

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)]'
Дэн

Меня устраивает. Но как, если я хочу заменить значения во ВСЕХ столбцах?
famargar

2
Единственный метод, который работал для меня из показанных ответов, был сделать прямую замену в Серии. Спасибо!
Dirigo

243

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 здесь: Замените значения в ряду панд через словарь эффективно

Ориентиры

Используя следующие данные с версией панды 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
Последний блок кода для этого ответа, безусловно, не самый элегантный, но этот ответ заслуживает некоторого доверия. Это на несколько порядков быстрее для больших словарей и не использует всю мою оперативную память. Он переназначил файл из 10000 строк, используя словарь, в котором было около 9 миллионов записей за полминуты. df.replaceФункции, в то время как аккуратные и полезно для маленькой dicts, разбились после запуска в течение 20 минут или около того .
Гриффин


@griffinc Спасибо за отзыв и обратите внимание, что с тех пор я обновил этот ответ гораздо более простым способом выполнения неполного случая (спасибо @jpp)
JohnE

1
mapтакже работает над индексом, где я не мог найти способ сделать этоreplace
Макс Генис

1
@AlexSB Я не могу дать полностью общий ответ, но я думаю, что карта будет намного быстрее и выполнит (я думаю) то же самое. Как правило, слияние будет медленнее, чем другие варианты, которые делают то же самое.
Джон

59

В вашем вопросе есть немного неясности. Существует как минимум три две интерпретации:

  1. ключи diотносятся к значениям индекса
  2. ключи diотносятся к df['col1']значениям
  3. ключи diотносятся к указателям (не вопрос ОП, но добавлены для забавы).

Ниже приведено решение для каждого случая.


Случай 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

Здесь первые и третьи ряды были изменены, потому что ключи в diэто 0и 2, которые при индексации на основе 0 Пайтона относятся к первым и третьим местам.


replaceодинаково хорошо, и, возможно, лучшее слово для того, что здесь происходит.
Дан Аллан

Разве опубликованный целевой фрейм данных ОП не устраняет неоднозначность? Тем не менее, этот ответ полезен, так что +1.
DSM

@DSM: Ой, вы правы, у Case3 нет возможности, но я не думаю, что целевой фрейм данных OP отличает Case1 от Case2, поскольку значения индекса равны значениям столбца.
unutbu

Как и многие другие опубликованные, метод @ DSM, к сожалению, не сработал для меня, но случай @ unutbu сработал. update()кажется немного глупым по сравнению с replace(), но по крайней мере это работает.
Джефф

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 есть принятый ответ, но кодирование, кажется, не работает для всех. Вот тот, который работает с текущей версией панд (0.23.4 от 8/2018):

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

Документы для панд. DataFrame.replace здесь .


У меня никогда не было проблем с запуском ответа DSM, и я предполагаю, что с учетом общего количества голосов большинство других людей тоже этого не сделали. Вы могли бы хотеть быть более конкретным о проблеме, с которой вы столкнулись. Может быть, это связано с вашими примерами данных, которые отличаются от данных DSM?
JohnE

Хм, возможно, проблема с версиями. Тем не менее, оба ответа здесь и сейчас.
от

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использовали Серию, когда вы .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})

Таким образом, вы можете в любой момент обратиться к исходной метке класса из label_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.

Следовательно, вы можете применить метод серии map().

Наконец, и я обнаружил это поведение благодаря U10, вы можете использовать всю серию в выражении .get (). Если только я неправильно понял его поведение и он последовательно обрабатывает серию, а не поразрядно.
На .get(x,x)счетах для значений, не говоря уже в словаре отображения , которое будет рассматриваться как Nan иного .map()методом


.transform()Обрабатывает каждый столбец в виде ряда. В отличие от .apply()чего передаются столбцы, агрегированные в DataFrame. Я только что попробовал, apply()работает отлично. Там нет необходимости использовать loc, это кажется слишком сложным. df[["col1", "col2"]].apply(lambda col: col.map(lambda elem: my_dict.get(elem, elem)))должно работать просто отлично. На .get(x,x)счетах для значений, не говоря уже в словаре отображения , которое будет рассматриваться как Nan иного .map()способом Вы могли бы также использовать fillna()впоследствии.
AMC

Наконец, и я обнаружил это поведение благодаря U10, вы можете использовать всю серию в выражении .get (). Если только я неправильно понял его поведение и он последовательно обрабатывает серию, а не поразрядно. Я не могу воспроизвести это, вы можете уточнить? Переменные с одинаковыми именами, вероятно, играют здесь некоторую роль.
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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.