ทำการแมปค่าใหม่ในคอลัมน์หมีแพนด้าด้วย dict


318

ฉันมีพจนานุกรมที่มีลักษณะเช่นนี้: di = {1: "A", 2: "B"}

ฉันต้องการใช้กับคอลัมน์ "col1" ของ dataframe ที่คล้ายกับ:

     col1   col2
0       w      a
1       1      2
2       2    NaN

ที่จะได้รับ:

     col1   col2
0       w      a
1       A      2
2       B    NaN

ฉันจะทำสิ่งนี้ให้ดีที่สุดได้อย่างไร ด้วยเหตุผลบางอย่างของคำค้นหาของ Google ที่เกี่ยวข้องกับสิ่งนี้แสดงให้ฉันเห็นเฉพาะลิงก์เกี่ยวกับวิธีสร้างคอลัมน์จาก dicts และในทางกลับกัน: - /

คำตอบ:


341

.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

หรือโดยตรงบนคือSeriesdf["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
วิธีการเดียวที่ใช้ได้ผลกับคำตอบที่ฉันได้ทำคือการแทนที่โดยตรงในซีรี่ส์ ขอบคุณ!
Dirigo

243

map สามารถเร็วกว่า replace

หากพจนานุกรมของคุณมีมากกว่าสองสามปุ่มการใช้mapอาจเร็วกว่าreplaceมาก วิธีการนี้มีสองเวอร์ชันขึ้นอยู่กับว่าพจนานุกรมของคุณจับคู่ค่าที่เป็นไปได้ทั้งหมดอย่างละเอียดหรือไม่และคุณต้องการให้ค่าที่ไม่ตรงกันจับคู่หรือแปลงเป็น NaNs หรือไม่:

การทำแผนที่หมดจด

ในกรณีนี้ฟอร์มง่ายมาก:

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 ที่นี่: แทนที่ค่าในซีรีย์นุ่นผ่านพจนานุกรมได้อย่างมีประสิทธิภาพ

มาตรฐาน

การใช้ข้อมูลต่อไปนี้กับ pandas เวอร์ชั่น 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


@griffinc ขอบคุณสำหรับคำติชมและโปรดทราบว่าฉันได้อัปเดตคำตอบนี้ด้วยวิธีที่ง่ายกว่ามากในการทำเคสที่ไม่ละเอียด (ขอบคุณ @jpp)
JohnE

1
mapยังทำงานกับดัชนีที่ฉันไม่สามารถหาวิธีที่จะทำกับreplace
Max Ghenis

1
@AlexSB ฉันไม่สามารถให้คำตอบทั่วไปได้อย่างสมบูรณ์ แต่ฉันคิดว่าแผนที่จะเร็วขึ้นมากและทำให้สำเร็จ (ฉันคิดว่า) ในสิ่งเดียวกัน โดยทั่วไปการผสานจะช้ากว่าตัวเลือกอื่น ๆ ที่ทำสิ่งเดียวกัน
JohnE

59

คำถามของคุณมีความคลุมเครือเล็กน้อย มีการตีความอย่างน้อยสองสามประการ:

  1. กุญแจในการdiอ้างถึงค่าดัชนี
  2. กุญแจในการdiอ้างถึงdf['col1']ค่า
  3. กุญแจในการdiอ้างถึงตำแหน่งดัชนี (ไม่ใช่คำถามของ OP แต่โยนเพื่อความสนุก)

ด้านล่างเป็นวิธีแก้ปัญหาสำหรับแต่ละกรณี


กรณีที่ 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-based ธ หมายถึงสถานที่แรกและที่สาม


replaceดีพอ ๆ กันและอาจเป็นคำที่ดีกว่าสำหรับสิ่งที่เกิดขึ้นที่นี่
Dan Allan

ดาต้าเบสเป้าหมายที่ประกาศไว้ของ OP ไม่ได้กำจัดความคลุมเครือหรือไม่ ถึงกระนั้นคำตอบนี้มีประโยชน์ดังนั้น +1
DSM

@DSM: อ๊ะคุณถูกต้องแล้วไม่มีความเป็นไปได้ของ Case3 แต่ฉันไม่คิดว่า OPD datrrame เป้าหมายแตกต่าง Case1 จาก Case2 เนื่องจากค่าดัชนีเท่ากับค่าคอลัมน์
unutbu

เช่นเดียวกับคนอื่น ๆ จำนวนมากที่โพสต์วิธีการของ @ DSM น่าเสียดายที่ไม่ได้ผลสำหรับฉัน แต่กรณีของ @ unutbu 1 นั้นใช้งานได้ update()ดูเหมือน kludgy เล็กน้อยเมื่อเทียบกับreplace()แต่อย่างน้อยก็ใช้งานได้
เจฟฟ์

4

การเพิ่มคำถามนี้หากคุณมีมากกว่าหนึ่งคอลัมน์ที่จะทำการแมปใหม่ใน data datrame:

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 มีคำตอบที่ยอมรับ แต่การเข้ารหัสดูเหมือนจะไม่เหมาะกับทุกคน นี่คืออันที่ทำงานกับ pandas รุ่นปัจจุบัน (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

อืมอาจเป็นปัญหาเกี่ยวกับเวอร์ชัน อย่างไรก็ตามคำตอบทั้งสองอยู่ที่นี่ตอนนี้
Wordsforthewise

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) ที่คุณจะต้องระมัดระวังกับการแมปไม่ครบถ้วนสมบูรณ์ที่คุณตั้งใจจะ map NaNค่าที่เฉพาะเจาะจงเพื่อ วิธีการที่เหมาะสมในกรณีนี้ต้องให้คุณmaskซีรีส์เมื่อคุณอื่นคุณยกเลิกการทำแผนที่เพื่อ.fillnaNaN

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

map()ดังนั้นคุณสามารถใช้วิธีการที่ซีรีส์

ในที่สุดและฉันค้นพบพฤติกรรมนี้ด้วย U10 คุณสามารถใช้ซีรี่ส์ทั้งหมดในนิพจน์. get () เว้นแต่ว่าฉันเข้าใจผิดพฤติกรรมของมันและมันประมวลผลซีรีส์ตามลำดับแทนที่จะเป็นบิต บัญชีสำหรับค่าที่คุณไม่ได้พูดถึงในพจนานุกรมการทำแผนที่ของคุณซึ่งจะได้รับการพิจารณาเป็นอย่างอื่นน่านโดยวิธีการ
.get(x,x).map()


.transform()ประมวลผลแต่ละคอลัมน์เป็นชุด ตรงกันข้ามกับ.apply()ที่ส่งผ่านคอลัมน์ที่รวมใน DataFrame ฉันแค่พยายามใช้apply()งานได้ดี ไม่จำเป็นต้องใช้สิ่งlocใดสิ่งนี้ดูซับซ้อนเกินไป df[["col1", "col2"]].apply(lambda col: col.map(lambda elem: my_dict.get(elem, elem)))ควรทำงานได้ดี บัญชีสำหรับค่าที่คุณไม่ได้พูดถึงในพจนานุกรมการทำแผนที่ของคุณซึ่งจะได้รับการพิจารณาเป็นอย่างอื่นน่านโดยวิธีนอกจากนี้คุณยังสามารถใช้ในภายหลัง .get(x,x).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) 

เมื่อคุณกำหนดฟังก์ชันแล้วคุณสามารถนำไปใช้กับ dataframe ของคุณได้

di = {1: "A", 2: "B"}
df['col1'] = df.apply(lambda row: multiple_replace(di, row['col1']), axis=1)

วิธีการแบบดั้งเดิมของแพนด้านั้นคือการใช้ฟังก์ชั่นการแทนที่ดังนี้ "แบบดั้งเดิม" (สำนวน) มากกว่าวิธีการที่เรียบง่ายกว่าของ Pandas อย่างไร?
AMC
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.