Gấu trúc: tạo hai cột mới trong khung dữ liệu với các giá trị được tính toán từ một cột đã có trước


100

Tôi đang làm việc với thư viện gấu trúc và tôi muốn thêm hai cột mới vào khung dữ liệu dfcó n cột (n> 0).
Các cột mới này là kết quả của việc áp dụng một hàm cho một trong các cột trong khung dữ liệu.

Chức năng áp dụng như sau:

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

Một phương pháp để tạo cột mới cho một hàm chỉ trả về giá trị là:

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

Vì vậy, những gì tôi muốn, và đã cố gắng không thành công (*), là những thứ như:

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

Cách tốt nhất để thực hiện điều này có thể là gì? Tôi đã quét tài liệu mà không có manh mối nào.

** df['column_A'].map(calculate)trả về Sê-ri gấu trúc mỗi mục bao gồm một bộ z, y. Và cố gắng gán điều này cho hai cột khung dữ liệu sẽ tạo ra một ValueError. *

Câu trả lời:


119

Tôi chỉ muốn sử dụng 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

Cảm ơn, tuyệt vời, nó hoạt động. Tôi tìm thấy điều gì như thế này trong các tài liệu cho 0.8.1 ... Tôi cho rằng tôi nên lúc nào cũng nghĩ về dòng như danh sách của các bộ ...
Joaquin

Có bất kỳ sự khác biệt nào về hiệu suất wrt khi làm việc này không? zip (* bản đồ (tính toán, df ["a"])) thay vì zip (* df ["a"]. map (tính toán)), cũng cho (như trên) [(2, 4, 6), ( 3, 6, 9)]?
ekta

1
Tôi nhận được cảnh báo sau khi tạo cột mới như vậy: "SettingWithCopyWarning: Một giá trị đang cố gắng được đặt trên bản sao của một lát từ DataFrame. Thay vào đó, hãy thử sử dụng .loc [row_indexer, col_indexer] = value". Tôi có nên lo lắng về điều đó? pandas v.0.15
taras

46

Câu trả lời hàng đầu là thiếu sót theo ý kiến ​​của tôi. Hy vọng rằng không ai nhập hàng loạt tất cả gấu trúc vào không gian tên của chúng với from pandas import *. Ngoài ra, mapphương thức nên được dành riêng cho những thời điểm khi chuyển nó vào từ điển hoặc Chuỗi. Nó có thể có một chức năng nhưng đây là những gì applyđược sử dụng.

Vì vậy, nếu bạn phải sử dụng cách tiếp cận trên, tôi sẽ viết nó như thế này

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

Thực ra không có lý do gì để sử dụng zip ở đây. Bạn chỉ có thể làm điều này:

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

Phương pháp thứ hai này cũng nhanh hơn nhiều trên DataFrames lớn hơn

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

DataFrame được tạo với 300.000 hàng

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

Nhanh hơn 60 lần so với zip


Nói chung, tránh sử dụng apply

Áp dụng thường không nhanh hơn nhiều so với việc lặp qua danh sách Python. Hãy kiểm tra hiệu suất của vòng lặp for để làm điều tương tự như trên

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

Vì vậy, tốc độ này chậm gấp đôi, đây không phải là một hồi quy hiệu suất khủng khiếp, nhưng nếu chúng ta điều khiển mạng ở trên, chúng ta sẽ có được hiệu suất tốt hơn nhiều. Giả sử, bạn đang sử dụng 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)

Chỉ định trực tiếp mà không cần áp dụng

Bạn có thể nhận được những cải tiến tốc độ thậm chí lớn hơn nếu bạn sử dụng các hoạt động vector trực tiếp.

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

Điều này tận dụng các hoạt động vector hóa cực kỳ nhanh của NumPy thay vì các vòng lặp của chúng tôi. Bây giờ chúng tôi có tốc độ tăng gấp 30 lần so với ban đầu.


Kiểm tra tốc độ đơn giản nhất với apply

Ví dụ trên sẽ cho thấy rõ mức độ chậm applycó thể xảy ra, nhưng để nó rõ ràng hơn, hãy xem ví dụ cơ bản nhất. Hãy bình phương một Chuỗi 10 triệu số có và không áp dụng

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)

Không áp dụng nhanh hơn 50 lần

%timeit s ** 2
66 ms ± 2 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

1
Đây là một câu trả lời thực sự tuyệt vời. Tôi muốn hỏi: bạn nghĩ gì về applymaptrường hợp bạn phải triển khai một chức năng cụ thể cho từng phần tử của khung dữ liệu?
David

3
Mặc dù có một số lời khuyên hữu ích trong câu trả lời này, tôi tin rằng lời khuyên chính để sử dụng func(series)thay vì series.apply(func)chỉ áp dụng khi func được xác định hoàn toàn bằng cách sử dụng các phép toán hoạt động tương tự trên cả giá trị riêng lẻ và trên Chuỗi. Đó là trường hợp trong ví dụ trong câu trả lời đầu tiên, nhưng nó không phải là trường hợp trong câu hỏi của OP, câu hỏi này hỏi một cách tổng quát hơn về việc áp dụng các hàm cho các cột. 1/2
Graham Lea

1
Ví dụ, nếu df là: DataFrame({'a': ['Aaron', 'Bert', 'Christopher'], 'b': ['Bold', 'Courageous', 'Distrusted']})calcis: def calc(x): return x[0], len(x)thì tdf.a.apply(calc))calc(tdf.a)trả về những thứ rất khác nhau.
Graham Lea
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.