Значення Remap у стовпчику панд з диктатом


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

Як я можу найкраще це зробити? Чомусь терміни googling, пов’язані з цим, показують мені лише посилання про те, як робити стовпці з диктів і навпаки: - /

Відповіді:


340

Можна використовувати .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' '' '
Pengju 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
Єдиний метод, який працював для мене із наведених відповідей, - це зробити пряму заміну на серії. Дякую!
Діріго

242

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
Останній блок коду на цю відповідь, звичайно, не найвишуканіший, але ця відповідь заслуговує на деяку заслугу. Це на порядок швидше для великих словників і не використовує всю мою оперативну пам'ять. Він перезаписав 10 000 рядкових файлів за допомогою словника, який мав близько 9 мільйонів записів за півхвилини. df.replaceФункції, в той час як акуратні і корисно для маленької dicts, розбилися після запуску в протягом 20 хвилин або близько того .
гриффінк


@griffinc Дякую за відгук та зауважую, що я з тих пір оновив цю відповідь набагато простішим способом зробити невичерпний випадок (дякую @jpp)
JohnE

1
mapтакож працює над індексом, де я не міг розібратися із способом зробити це зreplace
Макс Ghenis

1
@AlexSB Я не можу дати повністю загальної відповіді, але я думаю, що карта була б набагато швидшою і виконала (я думаю) те саме. Як правило, злиття відбувається повільніше, ніж інші варіанти, які роблять те саме.
JohnE

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-базованою індексацією Python відносяться до першого і третього місця.


replaceоднаково добре, і, можливо, краще слово для того, що тут відбувається.
Ден Аллан

Чи не розміщена цільова рамка даних ОП не виправляє двозначність? Але ця відповідь корисна, тому +1.
DSM

@DSM: На жаль, ви маєте рацію, немає можливості Case3, але я не думаю, що цільова рамка даних OP відрізняє Case1 від Case2, оскільки значення індексу дорівнюють значенням стовпців.
unutbu

Як і ряд інших публікацій, метод @ DSM, на жаль, не працював для мене, але випадок @ unutbu 1 справді спрацював. 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

Документи для pandas.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})

Таким чином, ви можете в будь-якій точці звернутися до оригінальної мітки класу з 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 (). Якщо я неправильно зрозумів її поведінку, і вона обробляє послідовно серію, а не побіжно.
На .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.