เปลี่ยนค่าหนึ่งตามค่าอื่นในแพนด้า


109

ฉันพยายามตั้งโปรแกรมรหัส Stata ของฉันใหม่ใน Python เพื่อปรับปรุงความเร็วและฉันถูกชี้ไปในทิศทางของ PANDAS อย่างไรก็ตามฉันมีช่วงเวลาที่ยากลำบากในการคิดวิธีประมวลผลข้อมูล

สมมติว่าฉันต้องการวนซ้ำค่าทั้งหมดในหัวคอลัมน์ 'ID' หาก ID นั้นตรงกับตัวเลขที่ระบุฉันต้องการเปลี่ยนค่าที่ตรงกันสองค่า FirstName และ LastName

ใน Stata มีลักษณะดังนี้:

replace FirstName = "Matt" if ID==103
replace LastName =  "Jones" if ID==103

ดังนั้นสิ่งนี้จะแทนที่ค่าทั้งหมดใน FirstName ที่สอดคล้องกับค่า ID == 103 ถึง Matt

ในแพนดาสฉันกำลังลองอะไรแบบนี้

df = read_csv("test.csv")
for i in df['ID']:
    if i ==103:
          ...

ไม่แน่ใจว่าจะไปจากที่นี่ ความคิดใด ๆ ?

คำตอบ:


184

ทางเลือกหนึ่งคือใช้คุณสมบัติการแบ่งส่วนและการทำดัชนีของ Python เพื่อประเมินสถานที่ที่เงื่อนไขของคุณเก็บและเขียนทับข้อมูลที่นั่นอย่างมีเหตุผล

สมมติว่าคุณสามารถโหลดข้อมูลของคุณได้โดยตรงpandasด้วยpandas.read_csvรหัสต่อไปนี้อาจเป็นประโยชน์สำหรับคุณ

import pandas
df = pandas.read_csv("test.csv")
df.loc[df.ID == 103, 'FirstName'] = "Matt"
df.loc[df.ID == 103, 'LastName'] = "Jones"

ดังที่ได้กล่าวไว้ในความคิดเห็นคุณยังสามารถกำหนดให้กับทั้งสองคอลัมน์ได้ในช็อตเดียว:

df.loc[df.ID == 103, ['FirstName', 'LastName']] = 'Matt', 'Jones'

โปรดทราบว่าคุณจะต้องpandasใช้เวอร์ชัน 0.11 หรือใหม่กว่าเพื่อใช้ในlocการเขียนทับการดำเนินการมอบหมาย


อีกวิธีหนึ่งที่ทำได้คือใช้สิ่งที่เรียกว่าการมอบหมายงานที่ถูกล่ามโซ่ ลักษณะการทำงานของสิ่งนี้มีความเสถียรน้อยกว่าดังนั้นจึงไม่ถือว่าเป็นวิธีแก้ปัญหาที่ดีที่สุด (เป็นสิ่งที่ไม่พึงปรารถนาอย่างชัดเจนในเอกสาร) แต่ควรทราบเกี่ยวกับ:

import pandas
df = pandas.read_csv("test.csv")
df['FirstName'][df.ID == 103] = "Matt"
df['LastName'][df.ID == 103] = "Jones"

16
วิธีการเพิ่มรสชาตินี้:df.loc[df.ID == 103, ['FirstName', 'LastName']] = 'Matt', 'Jones'
Boud

2
-1 "อีกวิธีหนึ่งที่ทำได้คือใช้สิ่งที่เรียกว่าการมอบหมายงานที่ถูกล่ามโซ่" ไม่กึกไม่ได้ มันเป็นเพียงประโยชน์ที่จะรู้ว่าได้รับมอบหมายถูกล่ามโซ่ไม่น่าเชื่อถือ ไม่ใช่ว่าเป็นวิธีการแก้ปัญหาที่น่าเชื่อถือและไม่เหมาะสมสถานการณ์จะเลวร้ายกว่ามาก คุณได้รับการยอมรับแม้กระทั่งนี้ที่อื่น ๆ ในกองมากเกิน โปรดพยายามหลีกเลี่ยงการให้ภาพลวงตาว่างานที่ถูกล่ามโซ่เป็นทางเลือกที่ทำได้ สองวิธีแรกที่คุณให้ก็เพียงพอแล้วและเป็นวิธีที่ต้องการในการทำเช่นนี้
Phillip Cloud

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

11
อินเทอร์เน็ตเป็นธุรกิจที่ร้ายแรง ไม่ว่าจะด้วยวิธีใดก็ตาม EMS ฉันรู้สึกดีที่รู้ว่ามีตัวเลือก
Parseltongue

ปัญหาหนึ่งที่คุณอาจพบคือ csv มีจุด / จุดในชื่อคอลัมน์และงานที่มอบหมายจะยุ่งเหยิง คุณสามารถแก้ไขคอลัมน์โดยใช้สิ่งนี้: cols = df.columns cols = cols.map (lambda x: x.replace ('.', '_') ถ้า isinstance (x, str) else x) df.columns = cols
ski_squaw

37

คุณสามารถใช้mapมันสามารถแมป vales จาก Dictonairy หรือแม้แต่ฟังก์ชันที่กำหนดเอง

สมมติว่านี่คือ df ของคุณ:

    ID First_Name Last_Name
0  103          a         b
1  104          c         d

สร้างคำสั่ง:

fnames = {103: "Matt", 104: "Mr"}
lnames = {103: "Jones", 104: "X"}

และแผนที่:

df['First_Name'] = df['ID'].map(fnames)
df['Last_Name'] = df['ID'].map(lnames)

ผลลัพธ์จะเป็น:

    ID First_Name Last_Name
0  103       Matt     Jones
1  104         Mr         X

หรือใช้ฟังก์ชันแบบกำหนดเอง:

names = {103: ("Matt", "Jones"), 104: ("Mr", "X")}
df['First_Name'] = df['ID'].map(lambda x: names[x][0])

2
สิ่งนี้จะไม่สร้าง KeyError ถ้าไม่มีค่าในคำสั่งของคุณ?
EdChum

1
ฟังก์ชั่นที่กำหนดเองจะทำงานอื่น ๆ ต่อไป แต่ฉันคิดว่าสิ่งdictนี้ถูกสร้างขึ้นสำหรับการแมป มิฉะนั้นการตรวจสอบ / ทำความสะอาดบางอย่างสามารถทำได้ตาม:df.ID.isin(names.keys())
Rutger Kassies

ฟังก์ชันที่กำหนดเองสามารถขยายเป็นฟังก์ชันใดก็ได้ (ไม่ระบุชื่อ)
user989762

14

คำถามเดิมกล่าวถึงกรณีการใช้งานที่เฉพาะเจาะจง สำหรับผู้ที่ต้องการคำตอบทั่วไปเพิ่มเติมนี่คือตัวอย่างบางส่วน:

การสร้างคอลัมน์ใหม่โดยใช้ข้อมูลจากคอลัมน์อื่น

รับดาต้าเฟรมด้านล่าง:

import pandas as pd
import numpy as np

df = pd.DataFrame([['dog', 'hound', 5],
                   ['cat', 'ragdoll', 1]],
                  columns=['animal', 'type', 'age'])

In[1]:
Out[1]:
  animal     type  age
----------------------
0    dog    hound    5
1    cat  ragdoll    1

ด้านล่างนี้เรากำลังเพิ่มdescriptionคอลัมน์ใหม่เป็นการเรียงต่อกันของคอลัมน์อื่น ๆ โดยใช้การ+ดำเนินการที่ถูกแทนที่สำหรับชุดข้อมูล การจัดรูปแบบสตริงแฟนซี, f-strings ฯลฯ จะไม่ทำงานที่นี่เนื่องจาก+ใช้กับสเกลาร์ไม่ใช่ค่า 'ดั้งเดิม':

df['description'] = 'A ' + df.age.astype(str) + ' years old ' \
                    + df.type + ' ' + df.animal

In [2]: df
Out[2]:
  animal     type  age                description
-------------------------------------------------
0    dog    hound    5    A 5 years old hound dog
1    cat  ragdoll    1  A 1 years old ragdoll cat

เราได้รับ1 yearsสำหรับแมว (แทน1 year) ซึ่งเราจะแก้ไขด้านล่างโดยใช้เงื่อนไข

การแก้ไขคอลัมน์ที่มีอยู่ด้วยเงื่อนไข

ที่นี่เรากำลังแทนที่animalคอลัมน์เดิมด้วยค่าจากคอลัมน์อื่นและใช้np.whereเพื่อตั้งค่าสตริงย่อยตามเงื่อนไขตามค่าของage:

# append 's' to 'age' if it's greater than 1
df.animal = df.animal + ", " + df.type + ", " + \
    df.age.astype(str) + " year" + np.where(df.age > 1, 's', '')

In [3]: df
Out[3]:
                 animal     type  age
-------------------------------------
0   dog, hound, 5 years    hound    5
1  cat, ragdoll, 1 year  ragdoll    1

การแก้ไขหลายคอลัมน์ด้วยเงื่อนไข

วิธีการที่ยืดหยุ่นกว่าคือการเรียก.apply()ใช้ดาต้าเฟรมทั้งหมดแทนที่จะอยู่ในคอลัมน์เดียว:

def transform_row(r):
    r.animal = 'wild ' + r.type
    r.type = r.animal + ' creature'
    r.age = "{} year{}".format(r.age, r.age > 1 and 's' or '')
    return r

df.apply(transform_row, axis=1)

In[4]:
Out[4]:
         animal            type      age
----------------------------------------
0    wild hound    dog creature  5 years
1  wild ragdoll    cat creature   1 year

ในโค้ดด้านบนtransform_row(r)ฟังก์ชันจะใช้Seriesวัตถุที่เป็นตัวแทนของแถวที่กำหนด (ระบุโดยaxis=1ค่าเริ่มต้นของaxis=0จะระบุSeriesอ็อบเจ็กต์สำหรับแต่ละคอลัมน์) สิ่งนี้ช่วยลดความยุ่งยากในการประมวลผลเนื่องจากเราสามารถเข้าถึงค่า 'ดั้งเดิม' จริงในแถวโดยใช้ชื่อคอลัมน์และสามารถมองเห็นเซลล์อื่นในแถว / คอลัมน์ที่กำหนดได้


1
ขอขอบคุณที่สละเวลาเขียนคำตอบที่ครอบคลุมเช่นนี้ ชื่นชมมาก
Parseltongue

ขอบคุณสำหรับคำตอบที่เป็นประโยชน์อย่างยิ่งนี้ การติดตามผลหนึ่งครั้ง - จะเกิดอะไรขึ้นถ้าเราต้องการแก้ไขคอลัมน์โดยการคำนวณทางคณิตศาสตร์ในคอลัมน์แทนที่จะแก้ไขสตริง? ตัวอย่างเช่นเมื่อใช้ตัวอย่างข้างต้นจะเกิดอะไรขึ้นถ้าเราต้องการคูณคอลัมน์ df.age ด้วย 7 ถ้า df.animal == 'dog'? ขอบคุณ!
GbG

1
@GbG: np.whereอาจเป็นสิ่งที่คุณกำลังมองหาเช่นstackoverflow.com/a/42540310/191246แต่ก็เป็นไปได้เช่นกันว่าคุณจะไม่สามารถปรับตรรกะให้เข้ากับการดำเนินการสเกลาร์ได้ดังนั้นคุณจะต้องแปลงอย่างชัดเจน เซลล์มีตัวเลขคล้ายกับที่ทำในtransform_row
ccpizza

ขอบคุณ @ccpizza! สิ่งที่ฉันกำลังมองหา
GbG

13

คำถามนี้อาจยังคงมีผู้เยี่ยมชมบ่อยพอสมควรที่จะเสนอภาคผนวกสำหรับคำตอบของ Mr Kassies dictตัวในชั้นเรียนสามารถย่อยประเภทเพื่อให้เริ่มต้นจะถูกส่งกลับสำหรับ 'หายไป' กุญแจ กลไกนี้ใช้ได้ดีกับหมีแพนด้า แต่ดูด้านล่าง.

ด้วยวิธีนี้จึงเป็นไปได้ที่จะหลีกเลี่ยงข้อผิดพลาดที่สำคัญ

>>> import pandas as pd
>>> data = { 'ID': [ 101, 201, 301, 401 ] }
>>> df = pd.DataFrame(data)
>>> class SurnameMap(dict):
...     def __missing__(self, key):
...         return ''
...     
>>> surnamemap = SurnameMap()
>>> surnamemap[101] = 'Mohanty'
>>> surnamemap[301] = 'Drake'
>>> df['Surname'] = df['ID'].apply(lambda x: surnamemap[x])
>>> df
    ID  Surname
0  101  Mohanty
1  201         
2  301    Drake
3  401         

สิ่งเดียวกันสามารถทำได้ง่ายกว่าด้วยวิธีต่อไปนี้ การใช้อาร์กิวเมนต์ 'default' สำหรับgetเมธอดของวัตถุ dict ทำให้ไม่จำเป็นต้อง subclass a dict

>>> import pandas as pd
>>> data = { 'ID': [ 101, 201, 301, 401 ] }
>>> df = pd.DataFrame(data)
>>> surnamemap = {}
>>> surnamemap[101] = 'Mohanty'
>>> surnamemap[301] = 'Drake'
>>> df['Surname'] = df['ID'].apply(lambda x: surnamemap.get(x, ''))
>>> df
    ID  Surname
0  101  Mohanty
1  201         
2  301    Drake
3  401         

1
นี่เป็นคำตอบที่ดีที่สุดและง่ายที่สุดที่ฉันเคยเห็นมาพร้อมกับการจัดการเริ่มต้นที่ยอดเยี่ยม ขอบคุณ.
Brendan

@ เบรนแดน: โอ๊ย! ขอบคุณมาก ๆ.
Bill Bell
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.