cột gấu trúc GroupBy có giá trị NaN (thiếu)


147

Tôi có một DataFrame với nhiều giá trị bị thiếu trong các cột mà tôi muốn nhóm:

import pandas as pd
import numpy as np
df = pd.DataFrame({'a': ['1', '2', '3'], 'b': ['4', np.NaN, '6']})

In [4]: df.groupby('b').groups
Out[4]: {'4': [0], '6': [2]}

thấy rằng Pandas đã bỏ các hàng có giá trị đích NaN. (Tôi muốn bao gồm các hàng này!)

Vì tôi cần nhiều thao tác như vậy (nhiều cols bị thiếu giá trị) và sử dụng các hàm phức tạp hơn chỉ là trung vị (thường là rừng ngẫu nhiên), tôi muốn tránh viết các đoạn mã quá phức tạp.

Bất kỳ đề xuất? Tôi nên viết một chức năng cho việc này hay có một giải pháp đơn giản?


1
@PhillipCloud Tôi đã chỉnh sửa câu hỏi này để chỉ bao gồm câu hỏi, thực sự khá hay, liên quan đến việc tăng cường gấu trúc mở của Jeff's.
Andy Hayden

1
Không thể bao gồm (và tuyên truyền) NaN trong các nhóm là khá nghiêm trọng. Trích dẫn R là không thuyết phục, vì hành vi này không phù hợp với nhiều thứ khác. Dù sao, hack giả cũng khá tệ. Tuy nhiên, kích thước (bao gồm NaN) và số lượng (bỏ qua NaN) của một nhóm sẽ khác nhau nếu có NaN. dfgrouped = df.groupby (['b']). a.agg (['sum', 'size', 'Count']) dfgrouped ['sum'] [dfgrouped ['size']! = dfgrouped ['Count ']] = Không
Brian Preslopsky

Bạn có thể tóm tắt những gì bạn đang cố gắng để đạt được? tức là chúng ta thấy một đầu ra, nhưng đầu ra "mong muốn" là gì?
ca

2
Với gấu trúc 1.1 bạn sẽ sớm có thể xác định dropna=Falsetrong groupby()để có được kết quả mong muốn của bạn. Thêm thông tin
cs95

Câu trả lời:


130

Điều này được đề cập trong phần Dữ liệu bị thiếu của các tài liệu :

Các nhóm NA trong GroupBy được tự động loại trừ. Hành vi này phù hợp với R, ví dụ.

Một cách giải quyết khác là sử dụng trình giữ chỗ trước khi thực hiện nhóm (ví dụ -1):

In [11]: df.fillna(-1)
Out[11]: 
   a   b
0  1   4
1  2  -1
2  3   6

In [12]: df.fillna(-1).groupby('b').sum()
Out[12]: 
    a
b    
-1  2
4   1
6   3

Điều đó nói rằng, điều này cảm thấy hack khá khủng khiếp ... có lẽ nên có một tùy chọn để đưa NaN vào nhóm (xem vấn đề github này - sử dụng cùng một hack giữ chỗ).


4
Đây là một giải pháp hợp lý nhưng là một giải pháp hài hước mà tôi đã nghĩ đến trước đó, Pandas tạo ra các trường NaN từ những cái trống và chúng ta phải thay đổi chúng trở lại. Đây là lý do tôi nghĩ đến việc tìm kiếm các giải pháp khác như chạy máy chủ SQL và truy vấn các bảng từ đó (có vẻ hơi phức tạp) hoặc tìm một thư viện khác bất chấp Pandas hoặc sử dụng riêng của tôi (mà tôi muốn để thoát khỏi). Thx
Gyula Sámuel Karli

@ GyulaSámuelKarli Đối với tôi đây có vẻ là một lỗi nhỏ (xem phần bugreport ở trên) và giải pháp của tôi là một cách giải quyết. Tôi thấy lạ khi bạn viết ra toàn bộ thư viện.
Andy Hayden

1
Tôi không muốn viết ra Pandas chỉ cần tìm công cụ phù hợp với yêu cầu của tôi nhất.
Gyula Sámuel Karli

1
Hãy xem câu trả lời của tôi dưới đây, tôi tin rằng tôi đã tìm thấy một giải pháp khá tốt (sạch hơn, và có thể nhanh hơn). stackoverflow.com/a/43375020 / 408853
ca

4
Không, điều này không phù hợp với R. df%>% group_by cũng sẽ đưa ra các tóm tắt NA với cảnh báo có thể tránh được bằng cách chuyển cột nhóm qua fct_explicit_na và sau đó mức (Thiếu) được tạo.
Chăm sóc quấy rối

40

Chủ đề cổ xưa, nếu ai đó vẫn vấp phải điều này - một cách giải quyết khác là chuyển đổi qua .astype (str) thành chuỗi trước khi nhóm. Điều đó sẽ bảo tồn NaN.

in:
df = pd.DataFrame({'a': ['1', '2', '3'], 'b': ['4', np.NaN, '6']})
df['b'] = df['b'].astype(str)
df.groupby(['b']).sum()
out:
    a
b   
4   1
6   3
nan 2

@ K3 --- rnc: Xem bình luận cho liên kết của bạn - tác giả của bài đăng trong liên kết của bạn đã làm gì đó sai.
Thomas

@Thomas, vâng, chính xác như trong ví dụ trên. Vui lòng chỉnh sửa nếu bạn có thể làm cho ví dụ an toàn (và như tầm thường).
K3 --- rnc

Các sumsố alà chuỗi nối ở đây, không phải là tổng số. Điều này chỉ "hoạt động" vì 'b' bao gồm các mục riêng biệt. Bạn cần 'a' là số và 'b' là chuỗi
BallpointBen

28

gấu trúc> = 1.1

Từ gấu trúc 1.1, bạn sẽ kiểm soát tốt hơn hành vi này, các giá trị NA hiện được phép trong cá mú bằng cách sử dụng dropna=False:

pd.__version__
# '1.1.0.dev0+2004.g8d10bfb6f'

# Example from the docs
df

   a    b  c
0  1  2.0  3
1  1  NaN  4
2  2  1.0  3
3  1  2.0  2

# without NA (the default)
df.groupby('b').sum()

     a  c
b        
1.0  2  3
2.0  2  5
# with NA
df.groupby('b', dropna=False).sum()

     a  c
b        
1.0  2  3
2.0  2  5
NaN  1  4

Bạn có thể cài đặt phiên bản phát hành trước của v1.1 bằng lệnh sau:

pip install https://github.com/pandas-dev/pandas/releases/download/v1.1.0rc0/pandas-1.1.0rc0.tar.gz

4
Hy vọng câu trả lời này làm cho một cuộc diễu hành dần dần lên đến đỉnh. Đó là cách tiếp cận chính xác.
kdbanman

Tôi không nghĩ 1.1 đã được phát hành. Đã kiểm tra trên conda và pip và các phiên bản vẫn còn 1.0.4
sammywemmy

1
@sammywemmy Vâng, hiện tại điều này chỉ có thể được chạy trong môi trường phát triển . Tôi muốn có được một khởi đầu khi giới thiệu các tính năng mới cho các bài viết SO cũ. ;-)
cs95

9

Tôi không thể thêm nhận xét cho M. Kiewisch vì tôi không có đủ điểm danh tiếng (chỉ có 41 nhưng cần hơn 50 để nhận xét).

Dù sao, chỉ muốn chỉ ra rằng giải pháp M. Kiewisch không hoạt động như hiện tại và có thể cần điều chỉnh nhiều hơn. Xem xét ví dụ

>>> df = pd.DataFrame({'a': [1, 2, 3, 5], 'b': [4, np.NaN, 6, 4]})
>>> df
   a    b
0  1  4.0
1  2  NaN
2  3  6.0
3  5  4.0
>>> df.groupby(['b']).sum()
     a
b
4.0  6
6.0  3
>>> df.astype(str).groupby(['b']).sum()
      a
b
4.0  15
6.0   3
nan   2

cho thấy đối với nhóm b = 4.0, giá trị tương ứng là 15 thay vì 6. Ở đây, nó chỉ nối 1 và 5 dưới dạng chuỗi thay vì thêm nó dưới dạng số.


12
Đó là bởi vì bạn đã chuyển đổi toàn bộ DF thành str, thay vì chỉ bcột
Korem

Lưu ý rằng điều này đã được sửa trong câu trả lời được đề cập ngay bây giờ.
Shaido - Phục hồi Monica

1
Theo tôi, giải pháp mới tốt hơn nhưng vẫn không an toàn. Hãy xem xét một trường hợp trong đó một trong các mục trong cột 'b' giống như np.NaN được xâu chuỗi. Sau đó, những thứ đó được ghép lại với nhau. df = pd.DataFrame ({'a': [1, 2, 3, 5, 6], 'b': ['foo', np.NaN, 'bar', 'foo', 'nan']}) ; df ['b'] = df ['b']. astype (str); df.groupby (['b']). sum ()
Kamaraju Kusumanchi

6

Một điểm nhỏ cho giải pháp của Andy Hayden - nó không hoạt động (nữa?) Vì np.nan == np.nannăng suất False, vì vậy replacechức năng thực sự không làm gì cả.

Điều làm việc cho tôi là thế này:

df['b'] = df['b'].apply(lambda x: x if not np.isnan(x) else -1)

(Ít nhất đó là hành vi của Pandas 0.19.2. Xin lỗi để thêm nó dưới dạng một câu trả lời khác, tôi không có đủ danh tiếng để bình luận.)


12
Cũng có df['b'].fillna(-1).
K3 --- rnc

6

Tất cả các câu trả lời được cung cấp cho đến nay dẫn đến hành vi nguy hiểm tiềm tàng vì hoàn toàn có thể bạn chọn một giá trị giả thực sự là một phần của bộ dữ liệu. Điều này ngày càng có khả năng khi bạn tạo các nhóm có nhiều thuộc tính. Nói một cách đơn giản, cách tiếp cận không phải lúc nào cũng khái quát tốt.

Một giải pháp ít rắc rối hơn là sử dụng pd.drop_d repeatates () để tạo một chỉ mục duy nhất của các kết hợp giá trị với mỗi ID riêng của chúng, sau đó nhóm vào id đó. Nó dài dòng hơn nhưng hoàn thành công việc:

def safe_groupby(df, group_cols, agg_dict):
    # set name of group col to unique value
    group_id = 'group_id'
    while group_id in df.columns:
        group_id += 'x'
    # get final order of columns
    agg_col_order = (group_cols + list(agg_dict.keys()))
    # create unique index of grouped values
    group_idx = df[group_cols].drop_duplicates()
    group_idx[group_id] = np.arange(group_idx.shape[0])
    # merge unique index on dataframe
    df = df.merge(group_idx, on=group_cols)
    # group dataframe on group id and aggregate values
    df_agg = df.groupby(group_id, as_index=True)\
               .agg(agg_dict)
    # merge grouped value index to results of aggregation
    df_agg = group_idx.set_index(group_id).join(df_agg)
    # rename index
    df_agg.index.name = None
    # return reordered columns
    return df_agg[agg_col_order]

Lưu ý rằng bây giờ bạn có thể chỉ cần làm như sau:

data_block = [np.tile([None, 'A'], 3),
              np.repeat(['B', 'C'], 3),
              [1] * (2 * 3)]

col_names = ['col_a', 'col_b', 'value']

test_df = pd.DataFrame(data_block, index=col_names).T

grouped_df = safe_groupby(test_df, ['col_a', 'col_b'],
                          OrderedDict([('value', 'sum')]))

Điều này sẽ trả về kết quả thành công mà không phải lo lắng về việc ghi đè dữ liệu thực bị nhầm là giá trị giả.


Đây là giải pháp tốt nhất cho trường hợp chung, nhưng trong trường hợp tôi biết chuỗi / số không hợp lệ tôi có thể sử dụng thay vào đó, có lẽ tôi sẽ đi theo câu trả lời của Andy Hayden bên dưới ... Tôi hy vọng gấu trúc sẽ sớm khắc phục hành vi này.
Sarah Messer

4

Tôi đã trả lời điều này rồi, nhưng một số lý do câu trả lời đã được chuyển đổi thành một nhận xét. Tuy nhiên, đây là giải pháp hiệu quả nhất:

Không thể bao gồm (và tuyên truyền) NaN trong các nhóm là khá nghiêm trọng. Trích dẫn R là không thuyết phục, vì hành vi này không phù hợp với nhiều thứ khác. Dù sao, hack giả cũng khá tệ. Tuy nhiên, kích thước (bao gồm NaN) và số lượng (bỏ qua NaN) của một nhóm sẽ khác nhau nếu có NaN.

dfgrouped = df.groupby(['b']).a.agg(['sum','size','count'])

dfgrouped['sum'][dfgrouped['size']!=dfgrouped['count']] = None

Khi các giá trị này khác nhau, bạn có thể đặt giá trị trở về Không cho kết quả của hàm tổng hợp cho nhóm đó.


1
Điều này rất hữu ích với tôi nhưng nó trả lời một câu hỏi hơi khác so với câu hỏi ban đầu. IIUC, giải pháp của bạn truyền bá NaN trong tổng kết, nhưng các mục NaN trong cột "b" vẫn bị bỏ dưới dạng hàng.
Andrew

0

Đã cài đặt Pandas 1.1 tại Anaconda

Tôi không thể nhận xét về câu trả lời của cs95 nhưng anh ấy đã giúp tôi giải quyết vấn đề.

Tôi đã cố gắng cài đặt Pandas 1.1 nhưng không thành công khi sử dụng mã của anh ấy, vì vậy tôi đã googled và có thể cài đặt.

Trước tiên tôi chạy dấu nhắc anaconda với tư cách quản trị viên và dán đoạn mã sau:

pip install pandas==1.1.0rc0

Sau đó bao gồm sử dụng dropna = False

Liên kết: https://lologists.io/pypi/pandas

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.