2016-08-04 32 views
6

Cách nhanh nhất (trong giới hạn của tính linh động sane) là gì để đếm các giá trị riêng biệt, trên các cột của cùng một dtype, cho mỗi hàng trong một DataFrame?tính hiệu quả riêng biệt trên các cột của DataFrame, được nhóm theo hàng

Chi tiết: Tôi có DataFrame kết quả phân loại theo chủ đề (theo hàng) theo ngày (trong cột), tương tự với nội dung được tạo sau đây.

import numpy as np 
import pandas as pd 

def genSampleData(custCount, dayCount, discreteChoices): 
    """generate example dataset""" 
    np.random.seed(123)  
    return pd.concat([ 
       pd.DataFrame({'custId':np.array(range(1,int(custCount)+1))}), 
       pd.DataFrame(
       columns = np.array(['day%d' % x for x in range(1,int(dayCount)+1)]), 
       data = np.random.choice(a=np.array(discreteChoices), 
             size=(int(custCount), int(dayCount)))  
       )], axis=1) 

Ví dụ, nếu tập dữ liệu cho biết mỗi thức uống mà mỗi khách hàng đặt hàng trên mỗi lần đến cửa hàng, tôi muốn biết số lượng đồ uống riêng biệt cho mỗi khách hàng.

# notional discrete choice outcome   
drinkOptions, drinkIndex = np.unique(['coffee','tea','juice','soda','water'], 
            return_inverse=True) 

# integer-coded discrete choice outcomes 
d = genSampleData(2,3, drinkIndex) 
d 
# custId day1 day2 day3 
#0  1  1  4  1 
#1  2  3  2  1 

# Count distinct choices per subject -- this is what I want to do efficiently on larger DF 
d.iloc[:,1:].apply(lambda x: len(np.unique(x)), axis=1) 
#0 2 
#1 3 

# Note: I have coded the choices as `int` rather than `str` to speed up comparisons. 
# To reconstruct the choice names, we could do: 
# d.iloc[:,1:] = drinkOptions[d.iloc[:,1:]] 

Những gì tôi đã cố gắng: Các bộ dữ liệu trong trường hợp sử dụng này sẽ có nhiều đối tượng hơn ngày (ví dụ testDf dưới đây), vì vậy tôi đã cố gắng tìm ra hàng khôn ngoan hoạt động hiệu quả nhất:

testDf = genSampleData(100000,3, drinkIndex) 

#---- Original attempts ---- 
%timeit -n20 testDf.iloc[:,1:].apply(lambda x: x.nunique(), axis=1) 
# I didn't wait for this to finish -- something more than 5 seconds per loop 
%timeit -n20 testDf.iloc[:,1:].apply(lambda x: len(x.unique()), axis=1) 
# Also too slow 
%timeit -n20 testDf.iloc[:,1:].apply(lambda x: len(np.unique(x)), axis=1) 
#20 loops, best of 3: 2.07 s per loop 

Để cải thiện về nỗ lực ban đầu của tôi, chúng tôi lưu ý rằng pandas.DataFrame.apply() chấp nhận lập luận:

Nếu raw=True p Hàm assed sẽ nhận các đối tượng ndarray thay thế. Nếu bạn chỉ áp dụng một hàm giảm NumPy này sẽ đạt được hiệu suất tốt hơn

này đã cắt giảm thời gian chạy bằng hơn một nửa:

%timeit -n20 testDf.iloc[:,1:].apply(lambda x: len(np.unique(x)), axis=1, raw=True) 
#20 loops, best of 3: 721 ms per loop *best so far* 

Tôi rất ngạc nhiên rằng một giải pháp NumPy tinh khiết, mà dường như tương đương với ở trên với raw=True, thực sự chậm hơn một chút:

%timeit -n20 np.apply_along_axis(lambda x: len(np.unique(x)), axis=1, arr = testDf.iloc[:,1:].values) 
#20 loops, best of 3: 1.04 s per loop 

Cuối cùng, tôi cũng đã thử transpo hát dữ liệu để làm column-wise count distinct, mà tôi nghĩ có thể hiệu quả hơn (ít nhất là cho DataFrame.apply(), nhưng dường như không có sự khác biệt có ý nghĩa.

%timeit -n20 testDf.iloc[:,1:].T.apply(lambda x: len(np.unique(x)), raw=True) 
#20 loops, best of 3: 712 ms per loop *best so far* 
%timeit -n20 np.apply_along_axis(lambda x: len(np.unique(x)), axis=0, arr = testDf.iloc[:,1:].values.T) 
# 20 loops, best of 3: 1.13 s per loop 

Cho đến nay giải pháp tốt nhất của tôi là một sự pha trộn kỳ lạ của df.apply của len(np.unique()), nhưng những gì khác nên tôi cố gắng?

+0

Là đại diện ngày? Nó dường như ảnh hưởng đến sự khác biệt trong các buổi biểu diễn rất nhiều. – ayhan

+0

@ayhan thú vị ... số ngày là đại diện cho trường hợp sử dụng cụ thể của tôi nhưng nếu một cái gì đó khác hoạt động tốt hơn cho bộ dữ liệu rộng hơn sẽ đáng chú ý cho người dùng khác – C8H10N4O2

+0

Điều ngược lại thực sự. Có vẻ như so sánh từng cột với những người khác nhanh hơn nhiều khi bạn có một số lượng nhỏ các cột. Tôi đã đăng kết quả dưới dạng câu trả lời. – ayhan

Trả lời

3

sự hiểu biết của tôi là nunique được tối ưu hóa cho loạt lớn. Ở đây, bạn chỉ có 3 ngày.So sánh từng cột với các cột khác có vẻ nhanh hơn:

testDf = genSampleData(100000,3, drinkIndex) 
days = testDf.columns[1:] 

%timeit testDf.iloc[:, 1:].stack().groupby(level=0).nunique() 
10 loops, best of 3: 46.8 ms per loop 

%timeit pd.melt(testDf, id_vars ='custId').groupby('custId').value.nunique() 
10 loops, best of 3: 47.6 ms per loop 

%%timeit 
testDf['nunique'] = 1 
for col1, col2 in zip(days, days[1:]): 
    testDf['nunique'] += ~((testDf[[col2]].values == testDf.ix[:, 'day1':col1].values)).any(axis=1) 
100 loops, best of 3: 3.83 ms per loop 

Nó mất đi lợi thế khi bạn thêm nhiều cột hơn. Đối với số lượng cột khác nhau (cùng một thứ tự: stack().groupby(), pd.melt().groupby() và vòng lặp):

10 columns: 143ms, 161ms, 30.9ms 
50 columns: 749ms, 968ms, 635ms 
100 columns: 1.52s, 2.11s, 2.33s 
+0

wow, vòng lặp for for the win? – C8H10N4O2

+0

Có, vì vòng lặp đó chỉ chạy một vài lần. Tôi cũng đã thêm thời gian cho số cột cao hơn. – ayhan

+1

câu trả lời tuyệt vời! 1, tôi vẫn quay bánh xe của tôi nhấn mạnh vào một giải pháp tốt hơn. – piRSquared

2

pandas.melt với DataFrame.groupbygroupby.SeriesGroupBy.nunique dường như để thổi các giải pháp khác đi:

%timeit -n20 pd.melt(testDf, id_vars ='custId').groupby('custId').value.nunique() 
#20 loops, best of 3: 67.3 ms per loop 
+0

Có thể báo trước ở đây rằng việc này được nhóm theo giá trị cột chứ không phải số hàng. Nếu bạn không có biến id hàng duy nhất, bạn có thể muốn tính đến chi phí (nhỏ) của việc tạo một biến cho một điểm chuẩn công bằng. – C8H10N4O2

+1

Trong tất cả các giải pháp khác của bạn, vòng lặp 'apply' đã xảy ra trong python - ở đây' Groupby.nunique' sử dụng một số thủ thuật (xem [ở đây] (https://github.com/pydata/pandas/blob/master/pandas/ core/groupby.py # L2896)) để làm tất cả với các hoạt động được vector hóa. – chrisb

1

Bạn không cần custId. Tôi muốn stack, sau đó groupby

testDf.iloc[:, 1:].stack().groupby(level=0).nunique() 

enter image description here

Các vấn đề liên quan