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

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


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.


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 disą 0i 2, które przy indeksowaniu opartym na 0 w Pythonie odnoszą się do pierwszej i trzeciej lokalizacji.


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


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 .


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

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

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.


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


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

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.