Câu hỏi Pandas: tạo hai cột mới trong một khung dữ liệu với các giá trị được tính từ cột đã có trước


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

Các chức năng để áp dụng là như:

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

Một phương pháp để tạo cột mới cho 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à một cái gì đó 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 không có đầu mối.

*df['column_A'].map(calculate) trả về một loạt gấu trúc mỗi mục bao gồm một bộ t, z. Và cố gắng gán điều này cho hai cột dataframe tạo ra một ValueError. 


76
2017-09-10 17:17


gốc




Các câu trả lời:


Tôi chỉ 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

100
2017-09-10 17:20



Cảm ơn, tuyệt vời, nó hoạt động. Tôi thấy không có gì như thế này trong tài liệu cho 0.8.1 ... Tôi cho rằng tôi nên luôn luôn nghĩ về Series như danh sách các bộ dữ liệu ... - joaquin
Có bất kỳ sự khác biệt hiệu suất wrt về việc này thay vì? zip (* map (tính, 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
Tôi nhận được cảnh báo sau khi thực hiện tạo cột mới như sau: "SettingWithCopyWarning: Giá trị đang cố gắng được đặt trên bản sao của một lát từ một DataFrame. Hãy thử sử dụng .loc [row_indexer, col_indexer] = value thay thế." Tôi có nên lo lắng về điều đó không? gấu trúc v.0.15 - taras


Câu trả lời hàng đầu là thiếu sót trong quan điểm của tôi. Hy vọng rằng, không có ai nhập khẩu hàng loạt gấu trúc vào không gian tên của họ from pandas import *. Ngoài ra, map phương pháp nên được dành riêng cho những lần khi chuyển nó một từ điển hoặc Series. Nó có thể có 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))

Có thực sự không có lý do để sử dụng zip ở đây. Bạn chỉ cần 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 các Khung dữ liệu 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 zip 60 lần


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

Á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 để thực hiện 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, đây là hai lần như chậm mà 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 cythonize ở trên, chúng tôi nhận đượ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 áp dụng

Bạn có thể nhận được cải thiện tốc độ cao hơn nếu bạn sử dụng các hoạt động được vector hóa 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 lợi dụng hoạt động vectơ cực nhanh của NumPy thay vì các vòng lặp của chúng ta. Bây giờ chúng tôi có tăng tốc 30 lần so với bản gốc.


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

Ví dụ trên nên hiển thị rõ ràng cách chậm apply có thể, nhưng chỉ để rõ ràng hơn, hãy nhìn vào ví dụ cơ bản nhất. Hãy tạo một chuỗi 10 triệu con 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)

19
2017-11-03 18:08



Đâ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ề applymap cho trường hợp khi bạn phải triển khai một hàm cụ thể cho từng phần tử của khung dữ liệu? - David
Trong khi có một số lời khuyên tốt 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 hàm func được định nghĩa hoàn toàn bằng cách sử dụng các thao tác hoạt động tương tự trên cả giá trị riêng lẻ và trên một 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, mà là yêu cầu thông thường hơn về việc áp dụng các hàm cho các cột. 1/2 - Graham Lea
Ví dụ, nếu df là: DataFrame({'a': ['Aaron', 'Bert', 'Christopher'], 'b': ['Bold', 'Courageous', 'Distrusted']}) và calc Là: def calc(x): return x[0], len(x) sau đó tdf.a.apply(calc)) và calc(tdf.a) trả lại những thứ rất khác nhau. - Graham Lea