Ubah satu nilai berdasarkan nilai lain di panda

109

Saya mencoba memprogram ulang kode Stata saya ke Python untuk peningkatan kecepatan, dan saya diarahkan ke PANDAS. Saya, bagaimanapun, mengalami kesulitan memikirkan bagaimana memproses data.

Katakanlah saya ingin mengulang semua nilai di kepala kolom 'ID.' Jika ID itu cocok dengan nomor tertentu, maka saya ingin mengubah dua nilai yang sesuai FirstName dan LastName.

Di Stata terlihat seperti ini:

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

Jadi ini menggantikan semua nilai di FirstName yang sesuai dengan nilai ID == 103 ke Matt.

Di PANDAS, saya mencoba sesuatu seperti ini

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

Tidak yakin hendak kemana setelah ini. Ada ide?

Parseltongue
sumber

Jawaban:

182

Salah satu opsinya adalah menggunakan fitur pemotongan dan pengindeksan Python untuk mengevaluasi secara logis tempat di mana kondisi Anda menahan dan menimpa data di sana.

Dengan asumsi Anda dapat memuat data Anda langsung ke pandasdengan pandas.read_csvkemudian kode berikut mungkin berguna untuk Anda.

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

Seperti yang disebutkan di komentar, Anda juga dapat melakukan tugas ke kedua kolom dalam satu kesempatan:

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

Perhatikan bahwa Anda memerlukan pandasversi 0.11 atau yang lebih baru agar dapat digunakan locuntuk operasi penugasan timpa.


Cara lain untuk melakukannya adalah dengan menggunakan apa yang disebut penugasan berantai. Perilaku ini kurang stabil sehingga tidak dianggap sebagai solusi terbaik ( secara eksplisit tidak disarankan dalam dokumen), tetapi berguna untuk mengetahui tentang:

import pandas
df = pandas.read_csv("test.csv")
df['FirstName'][df.ID == 103] = "Matt"
df['LastName'][df.ID == 103] = "Jones"
ely
sumber
16
bagaimana kalau menambahkan juga rasa ini:df.loc[df.ID == 103, ['FirstName', 'LastName']] = 'Matt', 'Jones'
Boud
2
-1 "Cara lain untuk melakukannya adalah dengan menggunakan apa yang disebut tugas berantai." Tidak. Secara tegas, tidak. Ini hanya berguna untuk mengetahui bahwa tugas dirantai tidak dapat diandalkan. Bukan karena ini solusi yang andal dan tidak optimal, situasinya jauh lebih buruk . Anda bahkan telah mengakui ini di tempat lain di Stack Overflow . Harap mencoba untuk menghindari memberikan ilusi bahwa tugas yang dirantai adalah pilihan yang layak. Dua metode pertama yang Anda berikan sudah cukup, dan merupakan cara yang disukai untuk melakukan ini.
Phillip Cloud
9
Saya tidak setuju. Saya tidak mengerti mengapa Anda terus berusaha keras untuk menegaskan bahwa penugasan yang dirantai bukanlah cara yang layak. Saya mengakui bahwa itu tidak dianggap sebagai cara yang disukai. Apa lagi yang kamu inginkan. Ini masuk akal untuk bertindak seperti ini bukan sebuah cara untuk melakukannya. Faktanya, di sistem saya sekarang (versi 0.8), itu adalah cara yang tepat untuk melakukannya. Saya tidak tertarik dengan suara positif Anda jika Anda akan mengambil posisi ini. Jangan ragu untuk memberi tanda pada poin Anda dengan suara negatif, tetapi saya telah merefleksikan poin Anda dan tidak setuju dengannya.
ely
11
Internet adalah bisnis yang serius. Bagaimanapun, EMS, saya menghargai mengetahui opsi itu ada.
Parseltongue
Satu masalah yang mungkin Anda hadapi adalah bahwa csv memiliki titik / titik di nama kolom dan tugas menjadi kacau. Anda dapat memperbaiki kolom menggunakan sesuatu seperti ini: cols = df.columns cols = cols.map (lambda x: x.replace ('.', '_') If isinstance (x, str) else x) df.columns = cols
ski_squaw
37

Anda dapat menggunakannya map, itu dapat memetakan vales dari toko susu atau bahkan fungsi khusus.

Misalkan ini df Anda:

    ID First_Name Last_Name
0  103          a         b
1  104          c         d

Buat penisnya:

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

Dan peta:

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

Hasilnya adalah:

    ID First_Name Last_Name
0  103       Matt     Jones
1  104         Mr         X

Atau gunakan fungsi khusus:

names = {103: ("Matt", "Jones"), 104: ("Mr", "X")}
df['First_Name'] = df['ID'].map(lambda x: names[x][0])
Rutger Kassies
sumber
2
Tidakkah ini akan menghasilkan KeyError jika nilainya tidak ada di dict Anda?
EdChum
1
Fungsi khusus akan, yang lainnya tetap berfungsi. Tapi saya berasumsi dictdibuat untuk pemetaan. Jika tidak, beberapa pemeriksaan / pembersihan dapat dilakukan berdasarkan hal-hal seperti:df.ID.isin(names.keys())
Rutger Kassies
Fungsi kustom dapat diperluas menjadi fungsi (non anonim) apa pun.
pengguna989762
14

Pertanyaan asli membahas kasus penggunaan sempit tertentu. Bagi mereka yang membutuhkan jawaban yang lebih umum, berikut beberapa contohnya:

Membuat kolom baru menggunakan data dari kolom lain

Diberikan dataframe di bawah ini:

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

Di bawah ini kami menambahkan descriptionkolom baru sebagai rangkaian kolom lain dengan menggunakan +operasi yang diganti untuk seri. Pemformatan string mewah, f-string, dll. Tidak akan berfungsi di sini karena +berlaku untuk skalar dan bukan nilai 'primitif':

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

Kami mendapatkan 1 yearsuntuk kucing (bukan 1 year) yang akan kami perbaiki di bawah ini menggunakan kondisional.

Mengubah kolom yang sudah ada dengan kondisional

Di sini kami mengganti animalkolom asli dengan nilai dari kolom lain, dan menggunakan np.whereuntuk mengatur substring bersyarat berdasarkan nilai 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

Mengubah beberapa kolom dengan kondisional

Pendekatan yang lebih fleksibel adalah dengan memanggil .apply()seluruh kerangka data daripada pada satu kolom:

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

Dalam kode di atas transform_row(r)fungsi mengambil Seriesobjek yang mewakili baris tertentu (ditunjukkan oleh axis=1, nilai default axis=0akan menyediakan Seriesobjek untuk setiap kolom). Ini menyederhanakan pemrosesan karena kita dapat mengakses nilai 'primitif' aktual di baris menggunakan nama kolom dan memiliki visibilitas sel lain di baris / kolom tertentu.

ccpizza
sumber
1
Terima kasih telah meluangkan waktu untuk menulis jawaban yang komprehensif. Sangat dihargai.
Parseltongue
Terima kasih atas jawaban yang sangat membantu ini. Satu tindak lanjut - bagaimana jika kita ingin memodifikasi kolom dengan melakukan matematika pada kolom, daripada memodifikasi string? Misalnya, dengan menggunakan contoh di atas, bagaimana jika kita ingin mengalikan kolom df.age dengan 7 jika df.animal == 'dog'? Terima kasih!
GbG
1
@GbG: np.wheremungkin yang Anda cari, lihat misalnya stackoverflow.com/a/42540310/191246 tetapi mungkin juga Anda tidak dapat menyesuaikan logika ke dalam operasi skalar, maka Anda perlu mengubah secara eksplisit sel secara numerik mirip dengan yang dilakukan ditransform_row
ccpizza
Terima kasih @ccpizza! Hanya apa yang saya cari.
GbG
13

Pertanyaan ini mungkin masih cukup sering dikunjungi sehingga ada baiknya menawarkan tambahan pada jawaban Mr Kassies. The dictbuilt-in kelas dapat sub-digolongkan sehingga default dikembalikan untuk kunci 'hilang'. Mekanisme ini bekerja dengan baik untuk panda. Tapi lihat di bawah.

Dengan cara ini dimungkinkan untuk menghindari kesalahan kunci.

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

Hal yang sama dapat dilakukan dengan lebih sederhana dengan cara berikut. Penggunaan argumen 'default' untuk getmetode objek dict membuatnya tidak perlu membuat subclass 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         
Bill Bell
sumber
1
sejauh ini ini adalah jawaban terbaik dan termudah yang pernah saya lihat, dengan penanganan default yang sangat baik. Terima kasih.
Brendan
@Brendan: Oh! Terima kasih banyak.
Bill Bell