Ponownie mapuj wartości w kolumnie pandy za pomocą dyktafonu

317

Mam słownik, który wygląda następująco: di = {1: "A", 2: "B"}

Chciałbym zastosować go do kolumny „col1” ramki danych podobnej do:

     col1   col2
0       w      a
1       1      2
2       2    NaN

uzyskać:

     col1   col2
0       w      a
1       A      2
2       B    NaN

Jak najlepiej to zrobić? Z jakiegoś powodu hasła google związane z tym pokazują tylko linki o tym, jak tworzyć kolumny z nagrań i odwrotnie: - /

TheChymera
źródło

Odpowiedzi:

340

Możesz użyć .replace. Na przykład:

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

lub bezpośrednio na Series, tj df["col1"].replace(di, inplace=True).

DSM
źródło
1
Nie działa dla mnie, gdy col```` is tuple. The error info is nie można porównać typów 'ndarray (dtype = object)' i 'tuple' '' '
Pengju Zhao
18
Wygląda na to już nie działa w ogóle , co nie jest zaskakujące, biorąc pod uwagę odpowiedź była od 4 lat temu. To pytanie wymaga nowej odpowiedzi, biorąc pod uwagę, jak ogólna jest operacja ...
PrestonH,
2
@ PrestonH Działa idealnie dla mnie. Uruchamianie:'3.6.1 |Anaconda custom (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]'
Dan
Mi to pasuje. Ale co jeśli chcę zastąpić wartości we WSZYSTKICH kolumnach?
famargar
2
Jedyną metodą, która zadziałała dla mnie z pokazanych odpowiedzi, była bezpośrednia zamiana serii. Dzięki!
Dirigo,
242

map może być znacznie szybszy niż replace

Jeśli słownik zawiera więcej niż kilka kluczy, użycie mapmoże być znacznie szybsze niż replace. Istnieją dwie wersje tego podejścia, w zależności od tego, czy słownik wyczerpująco odwzorowuje wszystkie możliwe wartości (a także od tego, czy chcesz, aby niezgodności zachowywały swoje wartości lub były konwertowane na NaN):

Wyczerpujące mapowanie

W takim przypadku formularz jest bardzo prosty:

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

Chociaż mapnajczęściej przyjmuje funkcję jako argument, może alternatywnie wziąć słownik lub serię: Dokumentacja dla Pandas.series.map

Niewyczerpujące mapowanie

Jeśli masz niewyczerpujące odwzorowanie i chcesz zachować istniejące zmienne dla niepasujących, możesz dodać fillna:

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

jak w odpowiedzi @ jpp tutaj: Efektywnie zamieniaj wartości w serii pand za pomocą słownika

Benchmarki

Używanie następujących danych z pandami w wersji 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 ) })

i testowanie z %timeit, wydaje się, że mapjest około 10 razy szybsze niż replace.

Pamiętaj, że Twoje przyspieszenie mapróżni się w zależności od danych. Największe przyspieszenie wydaje się mieć duże słowniki i wyczerpujące zamienniki. Zobacz @jpp odpowiedź (link powyżej), aby uzyskać bardziej szczegółowe wyniki testów i dyskusji.

JohnE
źródło
17
Ostatni blok kodu dla tej odpowiedzi z pewnością nie jest najbardziej elegancki, ale ta odpowiedź zasługuje na uznanie. W przypadku dużych słowników jest on o rząd wielkości szybszy i nie zużywa całej pamięci RAM. Zmapowano plik 10.000 linii przy użyciu słownika zawierającego około 9 milionów wpisów w ciągu pół minuty. Ta df.replacefunkcja, chociaż uporządkowana i przydatna w przypadku małych nagrań, uległa awarii po około 20 minutach działania.
griffinc
@griffinc Dziękuję za opinie i zauważam, że od tego czasu zaktualizowałem tę odpowiedź o znacznie prostszy sposób na wykonanie niewyczerpującego przypadku (dzięki @jpp)
JohnE
1
mapdziała również na indeksie, w którym nie mogłem znaleźć sposobu, aby to zrobićreplace
Max Ghenis
1
@AlexSB Nie mogę udzielić całkowicie ogólnej odpowiedzi, ale myślę, że mapa byłaby znacznie szybsza i osiągnę (myślę) to samo. Ogólnie rzecz biorąc, scalanie będzie wolniejsze niż inne opcje, które robią to samo.
JohnE,
59

W twoim pytaniu jest trochę dwuznaczności. Istnieją co najmniej trzy dwie interpretacje:

  1. klawisze diodnoszą się do wartości indeksu
  2. klawisze diodnoszą się do df['col1']wartości
  3. klucze diodnoszą się do lokalizacji indeksu (nie pytanie OP, ale rzucono dla zabawy).

Poniżej znajduje się rozwiązanie dla każdego przypadku.


Przypadek 1: Jeśli klucze dimają odnosić się do wartości indeksu, możesz użyć updatemetody:

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

Na przykład,

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)

daje

  col1 col2
1    w    a
2    B   30
0    A  NaN

Zmodyfikowałem wartości z Twojego oryginalnego postu, aby było bardziej zrozumiałe update. Zwróć uwagę, w jaki sposób klucze disą powiązane z wartościami indeksu. Kolejność wartości indeksu - to znaczy lokalizacji indeksu - nie ma znaczenia.


Przypadek 2: Jeśli klucze diodnoszą się do df['col1']wartości, wówczas @DanAllan i @DSM pokazują, jak to osiągnąć za pomocą 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)

daje

  col1 col2
1    w    a
2    A   30
0    B  NaN

Zwróć uwagę, jak w tym przypadku klucze w dizostały zmienione, aby pasowały do wartości w df['col1'].


Przypadek 3: Jeśli klucze diodnoszą się do lokalizacji indeksu, możesz użyć

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

od

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)

daje

  col1 col2
1    A    a
2   10   30
0    B  NaN

Tutaj zmieniono pierwszy i trzeci wiersz, ponieważ klucze w di0i 2, które przy indeksowaniu opartym na 0 w Pythonie odnoszą się do pierwszej i trzeciej lokalizacji.

unutbu
źródło
replacejest równie dobre, a może lepsze słowo na to, co się tutaj dzieje.
Dan Allan
Czy opublikowana docelowa ramka danych PO nie eliminuje dwuznaczności? Mimo to ta odpowiedź jest przydatna, więc daje +1.
DSM
@DSM: Ups, masz rację, nie ma możliwości Case3, ale nie sądzę, że docelowa ramka danych OP odróżnia Case1 od Case2, ponieważ wartości indeksu są równe wartościom kolumny.
unutbu
Podobnie jak wiele innych opublikowanych, metoda @ DSM niestety nie działała dla mnie, ale przypadek @ unutbu 1 zadziałał. update()wydaje się trochę niechlujny w porównaniu do replace(), ale przynajmniej działa.
Geoff
4

Dodanie do tego pytania, jeśli kiedykolwiek masz więcej niż jedną kolumnę do zmiany mapowania w ramce danych:

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

Mam nadzieję, że może się przydać komuś.

Twoje zdrowie

Nico Coallier
źródło
1
Ta funkcja jest już zapewniona przez DataFrame.replace(), chociaż nie wiem, kiedy została dodana.
AMC
3

DSM ma zaakceptowaną odpowiedź, ale kodowanie nie działa dla wszystkich. Oto jedna, która działa z aktualną wersją pand (0.23.4 z 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())

Zobaczysz, że wygląda to tak:

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

Dokumenty dotyczące pandas.DataFrame.replace są tutaj .

słowami
źródło
Nigdy nie miałem problemu z uruchomieniem odpowiedzi DSM i domyślam się, że biorąc pod uwagę wysoki głos, większość innych ludzi też nie. Możesz sprecyzować problem, który masz. Może ma to związek z twoimi przykładowymi danymi innymi niż DSM?
JohnE
Hmm, może problem z wersjonowaniem. Niemniej jednak obie odpowiedzi są już dostępne.
terminem
1
Rozwiązanie w zaakceptowanej odpowiedzi działa tylko na niektórych typach, Series.map()wydaje się bardziej elastyczne.
AMC
2

Lub wykonaj apply:

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

Próbny:

>>> 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
>>> 
U10 do przodu
źródło
Co się stanie, gdy twój didyktando jest listą? Jak zmapować tylko jedną wartość na liście?
FaCoffee
Możesz, chociaż nie rozumiem dlaczego.
AMC
2

Podanie mapjest szybsze niż zastąpienie (rozwiązanie @ JohnE), należy zachować ostrożność przyNaN niewyczerpujących mapowaniach, w których zamierza się mapować określone wartości . Właściwa metoda w tym przypadku wymaga, abyś maskSeries, kiedy ty .fillna, w przeciwnym razie cofniesz mapowanie 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
ALollz
źródło
1

Ładne, kompletne rozwiązanie, które utrzymuje mapę twoich etykiet klas:

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

W ten sposób możesz w dowolnym momencie odwołać się do oryginalnej etykiety klasy z label_dict.

dorien
źródło
1

Jako rozszerzenie do tego, co zaproponował Nico Coallier (stosuje się do wielu kolumn) i U10-Forward (stosując metody stylu zastosuj) i podsumowując je do jednowierszowej proponuję:

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

.transform()Przetwarza każdą kolumnę jako serii. W przeciwieństwie do tego, .apply()co przekazuje kolumny zagregowane w DataFrame.

W związku z tym możesz zastosować metodę serii map().

Wreszcie i odkryłem to zachowanie dzięki U10, możesz użyć całej serii w wyrażeniu .get (). Chyba że źle zrozumiałem jego zachowanie i przetwarza serię sekwencyjnie zamiast bitowo.
Te .get(x,x)rachunki dla wartości nie wspomniałeś w swoim słowniku mapowania, które byłyby uznane za Nan inaczej .map()metody

LouisD
źródło
.transform()Przetwarza każdą kolumnę jako serii. W przeciwieństwie do tego, .apply()co przekazuje kolumny zagregowane w DataFrame. Właśnie próbowałem, apply()działa dobrze. Nie ma też potrzeby korzystania loc, wydaje się to zbyt skomplikowane. df[["col1", "col2"]].apply(lambda col: col.map(lambda elem: my_dict.get(elem, elem)))powinien działać dobrze. Na .get(x,x)kontach o wartości nie wspomniałeś w swoim słowniku mapowania, które byłyby uznane za Nan inaczej .map()metody Można również wykorzystać fillna()później.
AMC
Wreszcie i odkryłem to zachowanie dzięki U10, możesz użyć całej serii w wyrażeniu .get (). Chyba że źle zrozumiałem jego zachowanie i przetwarza serię sekwencyjnie zamiast bitowo. Nie mogę tego odtworzyć, możesz to rozwinąć? Zmienne o identycznej nazwie prawdopodobnie odgrywają tutaj pewną rolę.
AMC
0

Bardziej natywnym podejściem do pand jest zastosowanie funkcji zamiany, jak poniżej:

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) 

Po zdefiniowaniu funkcji można zastosować ją do ramki danych.

di = {1: "A", 2: "B"}
df['col1'] = df.apply(lambda row: multiple_replace(di, row['col1']), axis=1)
Amir Imani
źródło
Bardziej natywnym podejściem do pand jest zastosowanie funkcji zastępowania, jak poniżej. Jak to jest bardziej „natywne” (idiomatyczne?) Niż znacznie prostsze metody oferowane przez Pandas?
AMC