Remap nilai dalam kolom panda dengan dict

318

Saya memiliki kamus yang terlihat seperti ini: di = {1: "A", 2: "B"}

Saya ingin menerapkannya pada kolom "col1" dari dataframe yang mirip dengan:

     col1   col2
0       w      a
1       1      2
2       2    NaN

mendapatkan:

     col1   col2
0       w      a
1       A      2
2       B    NaN

Bagaimana saya bisa melakukan ini? Untuk beberapa alasan, istilah googling yang berkaitan dengan ini hanya menunjukkan kepada saya tautan tentang cara membuat kolom dari dicts dan sebaliknya: - /

TheChymera
sumber

Jawaban:

342

Anda bisa menggunakannya .replace. Sebagai contoh:

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

atau langsung pada Series, yaitu df["col1"].replace(di, inplace=True).

DSM
sumber
1
Itu tidak berfungsi untuk saya ketika jika col```` is tuple. The error info is Tidak dapat membandingkan jenis 'ndarray (dtype = objek)' dan 'tuple'```
Pengju Zhao
18
Sepertinya ini tidak berfungsi sama sekali , yang tidak mengejutkan mengingat jawabannya adalah dari 4 tahun yang lalu. Pertanyaan ini memerlukan jawaban baru mengingat seberapa umum operasinya ...
PrestonH
2
@ PrestonH Ini bekerja dengan baik untuk saya. Berjalan:'3.6.1 |Anaconda custom (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]'
Dan
Ini bekerja untuk saya. Tetapi bagaimana jika saya ingin mengganti nilai dalam SEMUA kolom?
famargar
2
Satu-satunya metode yang berfungsi untuk saya dari jawaban yang ditampilkan adalah melakukan penggantian langsung pada Seri. Terima kasih!
Dirigo
243

map bisa jauh lebih cepat daripada replace

Jika kamus Anda memiliki lebih dari beberapa kunci, menggunakan mapbisa jauh lebih cepat daripada replace. Ada dua versi dari pendekatan ini, tergantung pada apakah kamus Anda secara mendalam memetakan semua nilai yang mungkin (dan juga apakah Anda ingin yang tidak cocok mempertahankan nilainya atau dikonversi ke NaNs):

Pemetaan Lengkap

Dalam hal ini, formulirnya sangat sederhana:

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

Meskipun mappaling umum mengambil fungsi sebagai argumennya, ia dapat juga mengambil kamus atau seri: Documentation for Pandas.series.map

Pemetaan Tidak Lengkap

Jika Anda memiliki pemetaan yang tidak lengkap dan ingin mempertahankan variabel yang ada untuk yang tidak cocok, Anda dapat menambahkan fillna:

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

seperti pada jawaban @ jpp di sini: Ganti nilai dalam seri panda melalui kamus secara efisien

Tolak ukur

Menggunakan data berikut dengan panda versi 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 ) })

dan pengujian dengan %timeit, tampaknya mapsekitar 10x lebih cepat daripada replace.

Perhatikan bahwa speedup Anda mapakan berbeda dengan data Anda. Speedup terbesar tampaknya dengan kamus besar dan penggantian lengkap. Lihat jawaban @jpp (ditautkan di atas) untuk tolok ukur dan diskusi yang lebih luas.

JohnE
sumber
17
Blok kode terakhir untuk jawaban ini tentu bukan yang paling elegan, tetapi jawaban ini layak mendapat pujian. Ini adalah urutan besarnya lebih cepat untuk kamus besar dan tidak menggunakan semua RAM saya. Memetakan kembali 10.000 baris file menggunakan kamus yang memiliki sekitar 9 juta entri dalam waktu setengah menit. The df.replacefungsi, sementara rapi dan berguna untuk dicts kecil, jatuh setelah berjalan selama 20 menit atau lebih.
griffinc
@ griffinc Terima kasih atas umpan balik dan catat bahwa saya telah memperbarui jawaban ini dengan cara yang lebih sederhana untuk mengerjakan kasus yang tidak lengkap (terima kasih kepada @jpp)
JohnE
1
mapjuga bekerja pada indeks di mana saya tidak tahu cara untuk melakukan itu denganreplace
Max Ghenis
1
@AlexSB Saya tidak bisa memberikan jawaban yang sepenuhnya umum, tapi saya pikir peta akan jauh lebih cepat dan mencapai (saya pikir) hal yang sama. Secara umum, penggabungan akan lebih lambat daripada opsi lain yang melakukan hal yang sama.
JohnE
59

Ada sedikit ambiguitas dalam pertanyaan Anda. Setidaknya ada tiga dua interpretasi:

  1. kunci dalam dimerujuk pada nilai indeks
  2. kunci dimengacu pada df['col1']nilai
  3. kunci dalam dimerujuk ke lokasi indeks (bukan pertanyaan OP, tetapi dilemparkan untuk bersenang-senang.)

Di bawah ini adalah solusi untuk setiap kasus.


Kasus 1: Jika kunci didimaksudkan untuk merujuk ke nilai indeks, maka Anda dapat menggunakan updatemetode ini:

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

Sebagai contoh,

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)

hasil panen

  col1 col2
1    w    a
2    B   30
0    A  NaN

Saya telah memodifikasi nilai dari pos asli Anda sehingga lebih jelas apa updateyang dilakukan. Perhatikan bagaimana kunci diterkait dengan nilai indeks. Urutan nilai indeks - yaitu, lokasi indeks - tidak masalah.


Kasus 2: Jika kunci dimengacu pada df['col1']nilai, maka @DanAllan dan @DSM menunjukkan cara mencapai ini dengan 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)

hasil panen

  col1 col2
1    w    a
2    A   30
0    B  NaN

Perhatikan bagaimana dalam hal ini kunci didiubah untuk mencocokkan nilai dalam df['col1'].


Kasus 3: Jika kunci dimengacu pada lokasi indeks, maka Anda dapat menggunakannya

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

sejak

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)

hasil panen

  col1 col2
1    A    a
2   10   30
0    B  NaN

Di sini, baris pertama dan ketiga yang diubah, karena kunci dalam diadalah 0dan 2, yang dengan pengindeksan 0 berbasis Python mengacu pada lokasi pertama dan ketiga.

unutbu
sumber
replacesama baiknya, dan mungkin kata yang lebih baik untuk apa yang terjadi di sini.
Dan Allan
Tidakkah kerangka data target yang diposkan OP menghilangkan ambiguitas? Tetap saja, jawaban ini bermanfaat, jadi +1.
DSM
@ DSM: Ups, Anda benar tidak ada kemungkinan Case3, tapi saya tidak berpikir dataframe target OP membedakan Case1 dari Case2 karena nilai indeks sama dengan nilai kolom.
unutbu
Seperti beberapa orang lain yang diposting, metode @ DSM sayangnya tidak bekerja untuk saya, tetapi kasus @ unutbu 1 berhasil. update()tampaknya agak kumuh dibandingkan dengan replace(), tetapi setidaknya itu berhasil.
Geoff
4

Menambahkan ke pertanyaan ini jika Anda memiliki lebih dari satu kolom untuk dipetakan kembali dalam kerangka data data:

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

Semoga bermanfaat bagi seseorang.

Bersulang

Nico Coallier
sumber
1
Fungsi ini sudah disediakan oleh DataFrame.replace(), meskipun saya tidak tahu kapan itu ditambahkan.
AMC
3

DSM memiliki jawaban yang diterima, tetapi pengkodean tampaknya tidak berfungsi untuk semua orang. Berikut ini adalah yang berfungsi dengan versi panda saat ini (0.23.4 pada 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())

Anda akan melihatnya seperti:

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

Dokumen untuk panda . DataFrame.replace ada di sini .

kata selanjutnya
sumber
Saya tidak pernah memiliki masalah dalam mendapatkan jawaban DSM untuk menjalankan dan saya kira memberikan total suara tinggi kebanyakan orang lain tidak. Anda mungkin ingin lebih spesifik tentang masalah yang Anda hadapi. Mungkin itu ada hubungannya dengan data sampel Anda yang berbeda dari DSM?
JohnE
Hmm, mungkin masalah versi. Namun demikian, kedua jawaban ada di sini sekarang.
kata
1
Solusi dalam jawaban yang diterima hanya bekerja pada tipe tertentu, Series.map()tampaknya lebih fleksibel.
AMC
2

Atau lakukan apply:

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

Demo:

>>> 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-Maju
sumber
Apa yang terjadi ketika didict Anda adalah dict daftar? Bagaimana Anda bisa memetakan hanya satu nilai dalam daftar?
FaCoffee
Anda bisa, meskipun saya tidak mengerti mengapa Anda mau.
AMC
2

Diberikan maplebih cepat daripada mengganti (solusi @ JohnE) Anda harus berhati -NaN hati dengan pemetaan Non-Lengkap di mana Anda ingin memetakan nilai tertentu . Metode yang tepat dalam hal ini mengharuskan Anda maskmenjadi Seri saat Anda .fillna, jika tidak Anda membatalkan pemetaan 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
sumber
1

Solusi lengkap yang bagus yang menyimpan peta label kelas Anda:

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

Dengan cara ini, Anda dapat merujuk label kelas asli dari label_dict kapan saja.

dorien
sumber
1

Sebagai perpanjangan dari apa yang telah diusulkan oleh Nico Coallier (berlaku untuk beberapa kolom) dan U10-Forward (menggunakan gaya metode terapkan), dan meringkasnya menjadi satu-baris yang saya usulkan:

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

The .transform()memproses setiap kolom sebagai seri. Bertentangan dengan .apply()yang melewati kolom yang dikumpulkan dalam DataFrame.

Akibatnya, Anda dapat menerapkan metode Seri map().

Akhirnya, dan saya menemukan perilaku ini berkat U10, Anda dapat menggunakan seluruh Seri dalam ekspresi .get (). Kecuali jika saya salah memahami perilakunya dan memproses seri secara berurutan alih-alih dengan sedikit.
The .get(x,x)account untuk nilai-nilai Anda tidak menyebutkan dalam kamus pemetaan Anda yang akan dianggap sebagai Nan lain oleh .map()metode

louisD
sumber
The .transform()memproses setiap kolom sebagai seri. Bertentangan dengan .apply()yang melewati kolom yang dikumpulkan dalam DataFrame. Saya baru saja mencoba, apply()bekerja dengan baik. Tidak perlu menggunakan lockeduanya, ini tampaknya terlalu rumit. df[["col1", "col2"]].apply(lambda col: col.map(lambda elem: my_dict.get(elem, elem)))harus bekerja dengan baik. The .get(x,x)account untuk nilai-nilai Anda tidak menyebutkan dalam kamus pemetaan Anda yang akan dianggap sebagai Nan lain oleh .map()metode Anda juga bisa menggunakan fillna()sesudahnya.
AMC
Akhirnya, dan saya menemukan perilaku ini berkat U10, Anda dapat menggunakan seluruh Seri dalam ekspresi .get (). Kecuali jika saya salah memahami perilakunya dan memproses seri secara berurutan alih-alih dengan sedikit. Saya tidak bisa mereproduksi ini, bisakah Anda menguraikan? Variabel bernama identik kemungkinan memainkan beberapa peran di sini.
AMC
0

Pendekatan panda yang lebih asli adalah menerapkan fungsi ganti seperti di bawah ini:

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) 

Setelah Anda mendefinisikan fungsi, Anda bisa menerapkannya ke bingkai data Anda.

di = {1: "A", 2: "B"}
df['col1'] = df.apply(lambda row: multiple_replace(di, row['col1']), axis=1)
Amir Imani
sumber
Pendekatan panda yang lebih asli adalah dengan menerapkan fungsi ganti seperti di bawah. Bagaimana itu lebih "asli" (idiomatik?) Daripada metode yang lebih sederhana yang disediakan oleh Pandas?
AMC