Фильтрация данных по минимальному количеству значений в группах - PullRequest
2 голосов
/ 03 мая 2020

У меня есть следующая структура данных:

#----------------------------------------------------------#
# Generate dataframe mock example.

# define categorical column.
grps = pd.DataFrame(['a', 'a', 'a', 'b', 'b', 'b']) 

# generate dataframe 1.
df1 = pd.DataFrame([[3, 4, 6, 8, 10, 4], 
                   [5, 7, 2, 8, 9, 6], 
                   [5, 3, 4, 8, 4, 6]]).transpose()

# introduce nan into dataframe 1.
for col in df1.columns:
    df1.loc[df1.sample(frac=0.1).index, col] = np.nan

# generate dataframe 2.
df2 = pd.DataFrame([[3, 4, 6, 8, 10, 4], 
                   [5, 7, 2, 8, 9, 6], 
                   [5, 3, 4, 8, 4, 6]]).transpose()

# concatenate categorical column and dataframes.
df = pd.concat([grps, df1, df2], axis = 1)

# Assign column headers.
df.columns = ['Groups', 1, 2, 3, 4, 5, 6]

# Set index as group column.
df = df.set_index('Groups')

# Generate stacked dataframe structure.
test_stack_df = df.stack(dropna = False).reset_index() 

# Change column names.
test_stack_df = test_stack_df.rename(columns = {'level_1': 'IDs',
                                                0: 'Values'})

#----------------------------------------------------------#

Исходный кадр данных - 'df' перед суммированием:

Groups  1   2   3   4   5   6
a       3   5   5   3   5   5
a      nan nan  3   4   7   3
a       6   2  nan  6   2   4
b       8   8   8   8   8   8
b      10   9   4  10   9   4
b       4   6   6   4   6   6

Я бы хотел отфильтровать столбцы таким образом, чтобы в каждой группе было как минимум 3 допустимых значения - «a» и «b». Окончательный вывод должен быть только столбцы 4, 5, 6. В настоящее время я использую следующий метод:

# Function to define boolean series.
def filter_vals(test_stack_df, orig_df):
    # Reset index.
    df_idx_reset = orig_df.reset_index()

    # Generate list with size of each 'Group'.
    grp_num = pd.value_counts(df_idx_reset['Groups']).to_list()

    # Data series for each 'Group'.
    expt_class_1 = test_stack_df.head(grp_num[0])
    expt_class_2 = test_stack_df.tail(grp_num[1])

    # Check if both 'Groups' contain at least 3 values per 'ID'.
    valid_IDs = len(expt_class_1['Values'].value_counts()) >=3 & \
                len(expt_class_2['Values'].value_counts()) >=3

    # Return 'true' or 'false'
    return(valid_IDs)

# Apply function to dataframe to generate boolean series.
bool_series = test_stack_df.groupby('IDs').apply(filter_vals, df)

# Transpose original dataframe.
df_T = df.transpose()

# Filter by boolean series & transpose again.
df_filtered = df_T[bool_series].transpose()

Я мог бы добиться этого с минимальными усилиями, применяя метод pandas.dataframe.dropna() и используйте пороговое значение 6. Однако это не будет учитывать группы разных размеров или позволять мне указывать минимальное количество значений, которое делает текущий код.

Для больших фреймов данных, то есть более 4000 столбцов, код является немного медленным, т.е. занимает ~ 20 секунд, чтобы завершить процесс фильтрации. Я пробовал альтернативные методы, которые обращаются к исходному фрейму данных напрямую, используя groupby & transform, но не могут заставить что-либо работать.

Есть ли более простой и быстрый способ? Спасибо за ваше время!

РЕДАКТИРОВАТЬ: 03/05/2020 (15:58) - только что заметил что-то неясное в функции выше. Все еще работает, но уточнил имена переменных. Извините за путаницу!

1 Ответ

0 голосов
/ 03 мая 2020

Это поможет вам:

df.notna().groupby(level='Groups').sum(axis=0).ge(3).all(axis=0)

Выходы:

1    False
2    False
3    False
4     True
5     True
6     True
dtype: bool
...