Pandas: buat dua kolom baru dalam kerangka data dengan nilai yang dihitung dari kolom yang sudah ada sebelumnya

100

Saya bekerja dengan perpustakaan pandas dan saya ingin menambahkan dua kolom baru ke dataframe dfdengan n kolom (n> 0).
Kolom baru ini dihasilkan dari penerapan fungsi ke salah satu kolom di dataframe.

Fungsi yang akan diterapkan adalah seperti:

def calculate(x):
    ...operate...
    return z, y

Salah satu metode untuk membuat kolom baru untuk fungsi yang hanya mengembalikan nilai adalah:

df['new_col']) = df['column_A'].map(a_function)

Jadi, yang saya inginkan, dan saya coba tidak berhasil (*), adalah seperti:

(df['new_col_zetas'], df['new_col_ys']) = df['column_A'].map(calculate)

Apa cara terbaik untuk mencapai ini? Saya memindai dokumentasi tanpa petunjuk.

** df['column_A'].map(calculate)mengembalikan Seri pandas setiap item terdiri dari tupel z, y. Dan mencoba untuk menetapkan ini ke dua kolom dataframe menghasilkan ValueError. *

joaquin
sumber

Jawaban:

119

Saya hanya menggunakan zip:

In [1]: from pandas import *

In [2]: def calculate(x):
   ...:     return x*2, x*3
   ...: 

In [3]: df = DataFrame({'a': [1,2,3], 'b': [2,3,4]})

In [4]: df
Out[4]: 
   a  b
0  1  2
1  2  3
2  3  4

In [5]: df["A1"], df["A2"] = zip(*df["a"].map(calculate))

In [6]: df
Out[6]: 
   a  b  A1  A2
0  1  2   2   3
1  2  3   4   6
2  3  4   6   9
DSM
sumber
Terima kasih, bagus, berhasil. Saya tidak menemukan yang seperti ini di dokumen untuk 0.8.1 ... Saya kira saya harus selalu menganggap Seri sebagai daftar tuple ...
joaquin
Apakah ada perbedaan kinerja WRT saat melakukan ini? zip (* map (hitung, df ["a"])) bukan zip (* df ["a"]. map (hitung)), yang juga memberikan (seperti di atas) [(2, 4, 6), ( 3, 6, 9)]?
ekta
1
Saya mendapatkan peringatan berikut saat melakukan pembuatan kolom baru seperti itu: "SettingWithCopyWarning: Sebuah nilai mencoba disetel pada salinan potongan dari DataFrame. Coba gunakan .loc [row_indexer, col_indexer] = value sebagai gantinya." Haruskah saya khawatir tentang itu? pandas v.0.15
taras
46

Jawaban teratas menurut saya salah. Mudah-mudahan, tidak ada yang mengimpor semua panda secara massal ke namespace mereka dengan from pandas import *. Selain itu, mapmetode ini harus disediakan untuk saat-saat ketika meneruskan kamus atau Seri. Ini bisa mengambil fungsi tapi untuk itulah applydigunakan.

Jadi, jika Anda harus menggunakan pendekatan di atas, saya akan menulisnya seperti ini

df["A1"], df["A2"] = zip(*df["a"].apply(calculate))

Sebenarnya tidak ada alasan untuk menggunakan zip di sini. Anda cukup melakukan ini:

df["A1"], df["A2"] = calculate(df['a'])

Metode kedua ini juga jauh lebih cepat pada DataFrames yang lebih besar

df = pd.DataFrame({'a': [1,2,3] * 100000, 'b': [2,3,4] * 100000})

DataFrame dibuat dengan 300.000 baris

%timeit df["A1"], df["A2"] = calculate(df['a'])
2.65 ms ± 92.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit df["A1"], df["A2"] = zip(*df["a"].apply(calculate))
159 ms ± 5.24 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

60x lebih cepat dari zip


Secara umum, hindari penggunaan apply

Penerapan umumnya tidak lebih cepat daripada melakukan iterasi pada daftar Python. Mari kita uji performa for-loop untuk melakukan hal yang sama seperti di atas

%%timeit
A1, A2 = [], []
for val in df['a']:
    A1.append(val**2)
    A2.append(val**3)

df['A1'] = A1
df['A2'] = A2

298 ms ± 7.14 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Jadi ini dua kali lebih lambat yang bukan merupakan regresi kinerja yang buruk, tetapi jika kita melakukan cythonisasi di atas, kita mendapatkan kinerja yang jauh lebih baik. Dengan asumsi, Anda menggunakan ipython:

%load_ext cython

%%cython
cpdef power(vals):
    A1, A2 = [], []
    cdef double val
    for val in vals:
        A1.append(val**2)
        A2.append(val**3)

    return A1, A2

%timeit df['A1'], df['A2'] = power(df['a'])
72.7 ms ± 2.16 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Menugaskan secara langsung tanpa melamar

Anda bisa mendapatkan peningkatan kecepatan yang lebih besar jika Anda menggunakan operasi vektor langsung.

%timeit df['A1'], df['A2'] = df['a'] ** 2, df['a'] ** 3
5.13 ms ± 320 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Ini mengambil keuntungan dari operasi vektorisasi NumPy yang sangat cepat daripada loop kita. Kami sekarang memiliki percepatan 30x dari aslinya.


Tes kecepatan paling sederhana dengan apply

Contoh di atas harus dengan jelas menunjukkan betapa lambatnya applybisa, tetapi agar lebih jelas mari kita lihat contoh paling dasar. Mari kita kuadratkan Seri 10 juta angka dengan dan tanpa menerapkan

s = pd.Series(np.random.rand(10000000))

%timeit s.apply(calc)
3.3 s ± 57.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Tanpa penerapan, 50x lebih cepat

%timeit s ** 2
66 ms ± 2 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
Ted Petrou
sumber
1
Ini adalah jawaban yang sangat bagus. Saya ingin bertanya: apa yang Anda pikirkan applymapuntuk kasus ketika Anda harus mengimplementasikan fungsi tertentu untuk setiap elemen dataframe?
David
3
Meskipun ada beberapa saran bagus dalam jawaban ini, saya yakin saran utama untuk digunakan func(series)alih-alih series.apply(func)hanya berlaku jika fungsi sepenuhnya ditentukan menggunakan operasi yang berperilaku serupa pada nilai individu dan Seri. Itu adalah kasus dalam contoh di jawaban pertama, tetapi tidak demikian halnya dalam pertanyaan OP, yang menanyakan secara lebih umum tentang penerapan fungsi ke kolom. 1/2
Graham Lea
1
Sebagai contoh, jika df adalah: DataFrame({'a': ['Aaron', 'Bert', 'Christopher'], 'b': ['Bold', 'Courageous', 'Distrusted']})and calcis: def calc(x): return x[0], len(x)then tdf.a.apply(calc))dan calc(tdf.a)return sangat berbeda.
Graham Lea