Как подсчитать количество строк, содержащих как значение в наборе столбцов, так и другое значение в другом столбце в кадре данных Pandas? - PullRequest
5 голосов
/ 04 августа 2020
# import packages, set nan
import pandas as pd
import numpy as np
nan = np.nan

Проблема

У меня есть фрейм данных с определенным количеством наблюдений в виде столбцов, измерений в виде строк. Результаты наблюдений равны A, B, C, D .... В нем также есть столбец категория , который обозначает категорию из измерения . Категории: a, b, c, d .... Если столбец содержит nan в строке, это означает, что наблюдение во время этого измерения не проводилось (поэтому nan не observation, это его отсутствие). An MRE :

data = {'observation0': ['A','A','A','A','B'],'observation1': ['B','B','B','C',nan], 'category': ['a', 'b', 'c','a','b']}
df = pd.DataFrame.from_dict(data)

df выглядит так:

enter image description here

I would like to count how many times each observational result (ie A, B, C, D...) is observed using each category of measurement (ie a, b, c, d ...).

I would like to get:

obs_A_in_cat_a    2
obs_A_in_cat_b    1
obs_A_in_cat_c    1
obs_B_in_cat_a    1
obs_B_in_cat_b    2
obs_B_in_cat_c    1
obs_C_in_cat_a    1
obs_C_in_cat_b    0
obs_C_in_cat_c    0

Observation A appears in rows with index 0 and 3 (see above df) while the measurement category is a, so obs_A_in_cat_a is 2. Observation A appears only once (row index 1) in a measurement with category: b, so obs_A_in_cat_b is 1, and so on.


My solution

First I gather the outcomes of observations, стараясь не включать nans :

observations = pd.unique(pd.concat([df[col] for col in df.columns if 'observation' in col]).dropna())

Различные категории, к которым они принадлежат:

categories = pd.unique(df['category'])

Затем повторите наблюдения. Если он полагается на , это ,

for observation in observations:
    for category in categories:
        df['obs_'+observation+'_in_cat_'+category]=\
        df.apply(lambda row: int(observation in [row[col]
                                                 for col in df.columns
                                                 if 'observation' in col]
                                 and row['category'] == category),axis=1)

Лямбда-функция проверяет, появляется ли observation в каждом row, и что измерение находится в категории, которая в настоящее время рассматривается в итерация. Создаются новые столбцы с заголовками obs_OBSERVATION_in_cat_CATEGORY, где OBSERVATION равно A, B, C, D ..., CATEGORY равно a, b, c, d ... Если observationX в categoryY было сделано во время измерения, obs_OBSERVATIONX_in_cat_CATEGORYY равно 1 в строка, соответствующая этому измерению, иначе это 0.

Результирующий df (его части) выглядит так:

enter image description here

Finish using sum() ming значения вновь созданных столбцов, выбирая те, которые имеют понимание условного списка :

df[[col for col in df.columns if '_in_cat_' in col]].sum()

Это дает мне результат, который я хотел бы получить, как показано выше. Вся записная книжка здесь .

Вопрос

Кажется, этот метод работает, но он слишком медленный, чтобы его можно было легко применить в реальной жизни. Как я могу сделать это быстрее? Я ищу что-то вроде:

how_many_times_each_observation_was_made_using_each_category_of_measurement(
df,
list_of_observation_columns,
category_column)

Ответы [ 3 ]

5 голосов
/ 04 августа 2020

Решение с MultiIndex с DataFrame.melt, GroupBy.size для значений счетчика, добавьте 0 для отсутствующих комбинаций на Series.reindex:

s = df.melt('category').groupby(['value','category']).size()
s = s.reindex(pd.MultiIndex.from_product(s.index.levels), fill_value=0)
print (s)
value  category
A      a           2
       b           1
       c           1
B      a           1
       b           2
       c           1
C      a           1
       b           0
       c           0
dtype: int64

Последнее возможно сгладить на f-string s:

s.index = s.index.map(lambda x: f'obs_{x[0]}_in_cat_{x[1]}')   
print (s)
obs_A_in_cat_a    2
obs_A_in_cat_b    1
obs_A_in_cat_c    1
obs_B_in_cat_a    1
obs_B_in_cat_b    2
obs_B_in_cat_c    1
obs_C_in_cat_a    1
obs_C_in_cat_b    0
obs_C_in_cat_c    0
dtype: int64
4 голосов
/ 04 августа 2020

Вы можете объединить melt с crossstab , чтобы получить результат:

s = df.melt("category")
s = pd.crosstab(s.value, s.category).stack()
s.index = [f"obs_{first}_in_cat_{last}" for first, last in s.index]

s

obs_A_in_cat_a    2
obs_A_in_cat_b    1
obs_A_in_cat_c    1
obs_B_in_cat_a    1
obs_B_in_cat_b    2
obs_B_in_cat_c    1
obs_C_in_cat_a    1
obs_C_in_cat_b    0
obs_C_in_cat_c    0
dtype: int64
1 голос
/ 04 августа 2020

Вы можете сделать это следующим образом:

dfT = []
for colName in ['observation0','observation1']:
    df1 = df.groupby([colName,'category'])['category'].count().to_frame()
    df1.columns = ['count']
    df1 = df1.reset_index()
    df1['label'] = 'obs_'+df1[colName]+'_cat_'+df1['category']
    df1 = df1.loc[:,['label','count']]
    dfT.append(df1)

dfT = pd.concat(dfT,axis=0).reset_index(drop=True)
...