Cara menerapkan fungsi ke dua kolom bingkai data Pandas

368

Misalkan saya punya dfyang memiliki kolom'ID', 'col_1', 'col_2' . Dan saya mendefinisikan suatu fungsi:

f = lambda x, y : my_function_expression.

Sekarang saya ingin menerapkan fke dfdua kolom 'col_1', 'col_2'untuk elemen-bijaksana menghitung kolom baru 'col_3', agak seperti:

df['col_3'] = df[['col_1','col_2']].apply(f)  
# Pandas gives : TypeError: ('<lambda>() takes exactly 2 arguments (1 given)'

Bagaimana melakukan ?

** Tambahkan detail sampel seperti di bawah ini ***

import pandas as pd

df = pd.DataFrame({'ID':['1','2','3'], 'col_1': [0,2,3], 'col_2':[1,4,5]})
mylist = ['a','b','c','d','e','f']

def get_sublist(sta,end):
    return mylist[sta:end+1]

#df['col_3'] = df[['col_1','col_2']].apply(get_sublist,axis=1)
# expect above to output df as below 

  ID  col_1  col_2            col_3
0  1      0      1       ['a', 'b']
1  2      2      4  ['c', 'd', 'e']
2  3      3      5  ['d', 'e', 'f']
bigbug
sumber
4
dapatkah Anda menerapkan f langsung ke kolom: df ['col_3'] = f (df ['col_1'], df ['col_2'])
btel
1
akan berguna untuk mengetahui apa fyang dilakukan
tehmisvh
2
tidak, df ['col_3'] = f (df ['col_1'], df ['col_2']) tidak berfungsi. Untuk f hanya menerima input skalar, bukan input vektor. OK, Anda dapat menganggap f = lambda x, y: x + y. (tentu saja, f asli saya tidak sesederhana itu, kalau tidak saya bisa langsung df ['col_3'] = df ['col_1'] + df ['col_2'])
bigbug
1
Saya menemukan tanya jawab terkait di url di bawah, tetapi masalah saya menghitung kolom baru dengan dua kolom yang ada, bukan 2 dari 1. stackoverflow.com/questions/12356501/...
bigbug
Saya pikir respons saya stackoverflow.com/a/52854800/5447172 menjawab ini dengan cara yang paling Pythonic / Pandan, tanpa workarounds atau pengindeksan numerik. Ini menghasilkan persis output yang Anda butuhkan dalam contoh Anda.
ajrwhite

Jawaban:

291

Berikut adalah contoh penggunaan applypada dataframe, yang saya panggil axis = 1.

Perhatikan perbedaannya adalah bahwa alih-alih mencoba meneruskan dua nilai ke fungsi f, tulis ulang fungsi untuk menerima objek Seri panda, lalu indeks Seri untuk mendapatkan nilai yang dibutuhkan.

In [49]: df
Out[49]: 
          0         1
0  1.000000  0.000000
1 -0.494375  0.570994
2  1.000000  0.000000
3  1.876360 -0.229738
4  1.000000  0.000000

In [50]: def f(x):    
   ....:  return x[0] + x[1]  
   ....:  

In [51]: df.apply(f, axis=1) #passes a Series object, row-wise
Out[51]: 
0    1.000000
1    0.076619
2    1.000000
3    1.646622
4    1.000000

Bergantung pada kasus penggunaan Anda, terkadang bermanfaat untuk membuat groupobjek panda , dan kemudian digunakan applypada grup.

Seorang pria
sumber
Ya, saya mencoba menggunakan apply, tetapi tidak dapat menemukan ekspresi sintaksis yang valid. Dan jika setiap baris df unik, masih menggunakan groupby?
bigbug
Menambahkan contoh pada jawaban saya, harap ini melakukan apa yang Anda cari. Jika tidak, berikan fungsi contoh yang lebih spesifik karena sumberhasil diselesaikan dengan salah satu metode yang disarankan sejauh ini.
Aman
1
Tolong tempelkan kode Anda? Saya menulis ulang fungsi: def get_sublist (x): return mylist [x [1]: x [2] + 1] dan df ['col_3'] = df.apply (get_sublist, axis = 1) memberikan 'ValueError: operan bisa tidak bisa disiarkan bersama dengan bentuk (2) (3) '
bigbug
3
@ Aman: dengan Pandas versi 0.14.1 (dan mungkin sebelumnya), gunakan bisa menggunakan ekspresi lambda juga. Berikan dfobjek yang Anda tetapkan, pendekatan lain (dengan hasil yang setara) adalah df.apply(lambda x: x[0] + x[1], axis = 1).
Jubbles
2
@CanCeylan Anda hanya dapat menggunakan nama kolom dalam fungsi alih-alih indeks maka Anda tidak perlu khawatir tentang perubahan pesanan, atau dapatkan indeks dengan nama misalnya lihat stackoverflow.com/questions/13021654/…
Davos
167

Ada cara bersih dan satu-baris untuk melakukan ini di Panda:

df['col_3'] = df.apply(lambda x: f(x.col_1, x.col_2), axis=1)

Ini memungkinkan funtuk menjadi fungsi yang ditentukan pengguna dengan beberapa nilai input, dan menggunakan nama kolom (aman) daripada indeks numerik (tidak aman) untuk mengakses kolom.

Contoh dengan data (berdasarkan pertanyaan asli):

import pandas as pd

df = pd.DataFrame({'ID':['1', '2', '3'], 'col_1': [0, 2, 3], 'col_2':[1, 4, 5]})
mylist = ['a', 'b', 'c', 'd', 'e', 'f']

def get_sublist(sta,end):
    return mylist[sta:end+1]

df['col_3'] = df.apply(lambda x: get_sublist(x.col_1, x.col_2), axis=1)

Output dari print(df):

  ID  col_1  col_2      col_3
0  1      0      1     [a, b]
1  2      2      4  [c, d, e]
2  3      3      5  [d, e, f]

Jika nama kolom Anda berisi spasi atau berbagi nama dengan atribut dataframe yang ada, Anda bisa mengindeks dengan tanda kurung:

df['col_3'] = df.apply(lambda x: f(x['col 1'], x['col 2']), axis=1)
ajrwhite
sumber
2
Catatan, jika menggunakan axis=1dan Anda disebut kolom nameitu tidak akan benar-benar mengembalikan data kolom Anda tetapi index. Mirip dengan mendapatkan namedi groupby(). Saya memecahkan masalah ini dengan mengganti nama kolom saya.
Tom Hemmes
2
INI ITU! Saya tidak menyadari Anda bisa memasukkan fungsi yang ditentukan pengguna dengan beberapa parameter input ke lambdas. Penting untuk dicatat (saya pikir) bahwa Anda menggunakan DF.apply () daripada Series.apply (). Ini memungkinkan Anda mengindeks df menggunakan dua kolom yang Anda inginkan, dan meneruskan seluruh kolom ke dalam fungsi, tetapi karena Anda menggunakan apply (), itu menerapkan fungsi dengan cara elemen-elemen ke seluruh kolom. Cemerlang! Terima kasih telah mengirim!
Data-phile
1
AKHIRNYA! Kamu menyelamatkan hariku!
Mysterio
Saya percaya cara yang disarankan untuk melakukan ini adalah df.loc [:, 'new col'] = df.apply .....
valearner
@valearner Saya tidak berpikir ada alasan untuk memilih .locdalam contoh ini. Mungkin diperlukan jika Anda mengadaptasi ini ke pengaturan masalah lain (misalnya bekerja dengan irisan).
ajrwhite
86

Solusi sederhana adalah:

df['col_3'] = df[['col_1','col_2']].apply(lambda x: f(*x), axis=1)
sjm
sumber
1
bagaimana jawaban ini berbeda dengan pendekatan dalam pertanyaan: df ['col_3'] = df [['col_1', 'col_2']]. berlaku (f) hanya untuk mengonfirmasi, pendekatan dalam pertanyaan tidak berhasil karena poster tidak menentukan sumbu ini = 1, standarnya adalah sumbu = 0?
Lost1
1
Jawaban ini sebanding dengan jawaban @ Anman tetapi sedikit lebih licin. Dia membangun fungsi anonim yang mengambil iterable, dan membongkar itu sebelum meneruskannya ke fungsi f.
tiao
39

Pertanyaan yang menarik! jawaban saya seperti di bawah ini:

import pandas as pd

def sublst(row):
    return lst[row['J1']:row['J2']]

df = pd.DataFrame({'ID':['1','2','3'], 'J1': [0,2,3], 'J2':[1,4,5]})
print df
lst = ['a','b','c','d','e','f']

df['J3'] = df.apply(sublst,axis=1)
print df

Keluaran:

  ID  J1  J2
0  1   0   1
1  2   2   4
2  3   3   5
  ID  J1  J2      J3
0  1   0   1     [a]
1  2   2   4  [c, d]
2  3   3   5  [d, e]

Saya mengubah nama kolom menjadi ID, J1, J2, J3 untuk memastikan ID <J1 <J2 <J3, sehingga kolom ditampilkan dalam urutan yang benar.

Satu lagi versi singkat:

import pandas as pd

df = pd.DataFrame({'ID':['1','2','3'], 'J1': [0,2,3], 'J2':[1,4,5]})
print df
lst = ['a','b','c','d','e','f']

df['J3'] = df.apply(lambda row:lst[row['J1']:row['J2']],axis=1)
print df

sumber
23

Metode yang Anda cari adalah Series.combine. Namun, tampaknya beberapa perawatan harus dilakukan di sekitar tipe data. Dalam contoh Anda, Anda akan (seperti yang saya lakukan ketika menguji jawaban) secara naif menelepon

df['col_3'] = df.col_1.combine(df.col_2, func=get_sublist)

Namun, ini melempar kesalahan:

ValueError: setting an array element with a sequence.

Tebakan terbaik saya adalah sepertinya hasilnya hasilnya sama dengan seri yang memanggil metode (df.col_1 di sini). Namun, berikut ini berfungsi:

df['col_3'] = df.col_1.astype(object).combine(df.col_2, func=get_sublist)

df

   ID   col_1   col_2   col_3
0   1   0   1   [a, b]
1   2   2   4   [c, d, e]
2   3   3   5   [d, e, f]
JoeCondron
sumber
12

Cara Anda menulis f membutuhkan dua input. Jika Anda melihat pesan kesalahan itu mengatakan Anda tidak memberikan dua input ke f, hanya satu. Pesan kesalahan sudah benar.
Ketidakcocokan karena df [['col1', 'col2']] mengembalikan satu kerangka data tunggal dengan dua kolom, bukan dua kolom terpisah.

Anda perlu mengubah f sehingga dibutuhkan satu input, simpan frame data di atas sebagai input, lalu pisahkan menjadi x, y di dalam fungsi body. Kemudian lakukan apa pun yang Anda butuhkan dan kembalikan satu nilai.

Anda memerlukan tanda tangan fungsi ini karena sintaksnya adalah .apply (f) Jadi f perlu mengambil satu hal = dataframe dan bukan dua hal yang diharapkan oleh f saat ini.

Karena Anda belum memberikan isi f saya tidak bisa membantu dalam detail lagi - tetapi ini harus memberikan jalan keluar tanpa secara mendasar mengubah kode Anda atau menggunakan beberapa metode lain daripada menerapkan

Nitin
sumber
12

Saya akan memberikan suara untuk np.vectorize. Ini memungkinkan Anda untuk hanya mengambil lebih dari x jumlah kolom dan tidak berurusan dengan kerangka data dalam fungsi, jadi bagus untuk fungsi yang tidak Anda kontrol atau lakukan sesuatu seperti mengirim 2 kolom dan konstanta ke fungsi (yaitu col_1, col_2, 'foo').

import numpy as np
import pandas as pd

df = pd.DataFrame({'ID':['1','2','3'], 'col_1': [0,2,3], 'col_2':[1,4,5]})
mylist = ['a','b','c','d','e','f']

def get_sublist(sta,end):
    return mylist[sta:end+1]

#df['col_3'] = df[['col_1','col_2']].apply(get_sublist,axis=1)
# expect above to output df as below 

df.loc[:,'col_3'] = np.vectorize(get_sublist, otypes=["O"]) (df['col_1'], df['col_2'])


df

ID  col_1   col_2   col_3
0   1   0   1   [a, b]
1   2   2   4   [c, d, e]
2   3   3   5   [d, e, f]
Trae Wallace
sumber
1
Ini tidak benar-benar menjawab pertanyaan menggunakan panda.
mnky9800n
18
Pertanyaannya adalah "Bagaimana cara menerapkan fungsi ke dua kolom dataframe Pandas" tidak "Bagaimana menerapkan fungsi ke dua kolom dataframe Pandas hanya menggunakan metode Pandas" dan numpy adalah ketergantungan Pandas sehingga Anda harus tetap menginstalnya, jadi ini sepertinya keberatan yang aneh.
Trae Wallace
12

Mengembalikan daftar dari applyadalah operasi yang berbahaya karena objek yang dihasilkan tidak dijamin sebagai Seri atau DataFrame. Dan pengecualian mungkin dimunculkan dalam kasus-kasus tertentu. Mari kita telusuri contoh sederhana:

df = pd.DataFrame(data=np.random.randint(0, 5, (5,3)),
                  columns=['a', 'b', 'c'])
df
   a  b  c
0  4  0  0
1  2  0  1
2  2  2  2
3  1  2  2
4  3  0  0

Ada tiga kemungkinan hasil dengan mengembalikan daftar dari apply

1) Jika panjang daftar yang dikembalikan tidak sama dengan jumlah kolom, maka serangkaian daftar dikembalikan.

df.apply(lambda x: list(range(2)), axis=1)  # returns a Series
0    [0, 1]
1    [0, 1]
2    [0, 1]
3    [0, 1]
4    [0, 1]
dtype: object

2) Ketika panjang daftar yang dikembalikan sama dengan jumlah kolom maka DataFrame dikembalikan dan setiap kolom mendapatkan nilai yang sesuai dalam daftar.

df.apply(lambda x: list(range(3)), axis=1) # returns a DataFrame
   a  b  c
0  0  1  2
1  0  1  2
2  0  1  2
3  0  1  2
4  0  1  2

3) Jika panjang daftar yang dikembalikan sama dengan jumlah kolom untuk baris pertama tetapi memiliki setidaknya satu baris di mana daftar memiliki jumlah elemen yang berbeda dari jumlah kolom, ValueError dinaikkan.

i = 0
def f(x):
    global i
    if i == 0:
        i += 1
        return list(range(3))
    return list(range(4))

df.apply(f, axis=1) 
ValueError: Shape of passed values is (5, 4), indices imply (5, 3)

Menjawab masalah tanpa mendaftar

Menggunakan applydengan sumbu = 1 sangat lambat. Dimungkinkan untuk mendapatkan kinerja yang jauh lebih baik (terutama pada dataset yang lebih besar) dengan metode iteratif dasar.

Buat kerangka data yang lebih besar

df1 = df.sample(100000, replace=True).reset_index(drop=True)

Pengaturan waktu

# apply is slow with axis=1
%timeit df1.apply(lambda x: mylist[x['col_1']: x['col_2']+1], axis=1)
2.59 s ± 76.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

# zip - similar to @Thomas
%timeit [mylist[v1:v2+1] for v1, v2 in zip(df1.col_1, df1.col_2)]  
29.5 ms ± 534 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

@ Thomas menjawab

%timeit list(map(get_sublist, df1['col_1'],df1['col_2']))
34 ms ± 459 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
Ted Petrou
sumber
1
Sangat menyenangkan melihat jawaban yang sangat rinci dari tempat belajar.
Andrea Moro
7

Saya yakin ini tidak secepat solusi menggunakan operasi Pandas atau Numpy, tetapi jika Anda tidak ingin menulis ulang fungsi Anda, Anda dapat menggunakan peta. Menggunakan data contoh asli -

import pandas as pd

df = pd.DataFrame({'ID':['1','2','3'], 'col_1': [0,2,3], 'col_2':[1,4,5]})
mylist = ['a','b','c','d','e','f']

def get_sublist(sta,end):
    return mylist[sta:end+1]

df['col_3'] = list(map(get_sublist,df['col_1'],df['col_2']))
#In Python 2 don't convert above to list

Kami bisa memberikan argumen sebanyak yang kami inginkan ke dalam fungsi dengan cara ini. Output adalah apa yang kami inginkan

ID  col_1  col_2      col_3
0  1      0      1     [a, b]
1  2      2      4  [c, d, e]
2  3      3      5  [d, e, f]
Thomas
sumber
1
Ini sebenarnya jauh lebih cepat dari jawaban yang digunakan applydenganaxis=1
Ted Petrou
2

Contoh saya untuk pertanyaan Anda:

def get_sublist(row, col1, col2):
    return mylist[row[col1]:row[col2]+1]
df.apply(get_sublist, axis=1, col1='col_1', col2='col_2')
Qing Liu
sumber
2

Jika Anda memiliki kumpulan data yang sangat besar, maka Anda dapat menggunakan cara yang lebih mudah (lebih cepat) untuk melakukan ini menggunakan swifter:

import pandas as pd
import swifter

def fnc(m,x,c):
    return m*x+c

df = pd.DataFrame({"m": [1,2,3,4,5,6], "c": [1,1,1,1,1,1], "x":[5,3,6,2,6,1]})
df["y"] = df.swifter.apply(lambda x: fnc(x.m, x.x, x.c), axis=1)
durjoy
sumber
1

Saya kira Anda tidak ingin mengubah get_sublistfungsi, dan hanya ingin menggunakan applymetode DataFrame untuk melakukan pekerjaan itu. Untuk mendapatkan hasil yang Anda inginkan, saya telah menulis dua fungsi bantuan: get_sublist_listdan unlist. Seperti yang disarankan oleh nama fungsi, pertama-tama dapatkan daftar sublist, ekstrak kedua sublist itu dari daftar itu. Akhirnya, Kita perlu memanggil applyfungsi untuk menerapkan kedua fungsi tersebut ke df[['col_1','col_2']]DataFrame selanjutnya.

import pandas as pd

df = pd.DataFrame({'ID':['1','2','3'], 'col_1': [0,2,3], 'col_2':[1,4,5]})
mylist = ['a','b','c','d','e','f']

def get_sublist(sta,end):
    return mylist[sta:end+1]

def get_sublist_list(cols):
    return [get_sublist(cols[0],cols[1])]

def unlist(list_of_lists):
    return list_of_lists[0]

df['col_3'] = df[['col_1','col_2']].apply(get_sublist_list,axis=1).apply(unlist)

df

Jika Anda tidak menggunakan []untuk melampirkan get_sublistfungsi, maka get_sublist_listfungsi akan mengembalikan daftar polos, itu akan naik ValueError: could not broadcast input array from shape (3) into shape (2), seperti @Ted Petrou telah sebutkan.

allenyllee
sumber