#python #pandas
#python #pandas
Вопрос:
У меня есть многоиндексный фрейм данных, который выглядит следующим образом:
target_q_0 target_q_1 target_q_2 target_q_3 target_q_4
sample_nr event
1 0 0.086743 -1.085944 1.304110 -0.174707 -0.037001
1 0.086743 -1.085944 1.304110 -0.174707 -0.037001
2 0.086743 -1.085944 1.304110 -0.174707 -0.037001
3 0.086743 -1.085944 1.304110 -0.174707 -0.037001
4 0.086743 -1.085944 1.304110 -0.174707 -0.037001
2 0 0.092704 -1.123734 1.368322 -0.206030 -0.006364
1 0.092376 -1.121655 1.364788 -0.204306 -0.008050
2 0.092057 -1.119634 1.361355 -0.202632 -0.009688
3 0.091748 -1.117672 1.358021 -0.201005 -0.011279
3 0 0.092704 -1.123734 1.368322 -0.206030 -0.006364
Каждый образец может иметь разное количество событий.
Я хочу найти самую длинную выборку, то есть выборку с наибольшим количеством событий, и обнулить все остальные выборки, чтобы они соответствовали ей по длине.
Желаемый результат был бы таким:
target_q_0 target_q_1 target_q_2 target_q_3 target_q_4
sample_nr event
1 0 0.086743 -1.085944 1.304110 -0.174707 -0.037001
1 0.086743 -1.085944 1.304110 -0.174707 -0.037001
2 0.086743 -1.085944 1.304110 -0.174707 -0.037001
3 0.086743 -1.085944 1.304110 -0.174707 -0.037001
4 0.086743 -1.085944 1.304110 -0.174707 -0.037001
2 0 0.092704 -1.123734 1.368322 -0.206030 -0.006364
1 0.092376 -1.121655 1.364788 -0.204306 -0.008050
2 0.092057 -1.119634 1.361355 -0.202632 -0.009688
3 0.091748 -1.117672 1.358021 -0.201005 -0.011279
4 0 0 0 0 0
3 0 0.092704 -1.123734 1.368322 -0.206030 -0.006364
1 0 0 0 0 0
2 0 0 0 0 0
3 0 0 0 0 0
4 0 0 0 0 0
У меня есть рабочий способ сделать это, но он очень медленный.
def pad_df(df):
max_rows = df.index.get_level_values(1).max()
for sample, new_df in df.groupby(level=0):
new_df = (new_df.unstack(level=0).reindex(list(range(max_rows)),
fill_value=0))
new_df = new_df.stack('sample_nr').swaplevel(0, 1).sort_index()
df.loc[experiment_data.index.get_level_values(0) == sample] = new_df
Эта функция вызывается с моим полным фреймом данных experiment_data в качестве входных данных:
experiment_data = load_some_stuff()
pad_df(experiment_data)
Ответ №1:
Если не удалось найти волшебный метод, справедливой стратегией может быть предварительное выделение требуемого массива и заполнение его с помощью цикла for . Обычно это намного быстрее, чем прямые операции с фреймом данных.
Требуемый MultiIndex
для ответа массив может быть сгенерирован с использованием pd.MultiIndex.from_product()
в вашем случае, поскольку длина каждого уровня фиксирована.
Данные
import pandas as pd
from pandas import DataFrame
import io
import numpy as np
df = pd.read_csv(io.StringIO("""
sample_nr event target_q_0 target_q_1 target_q_2 target_q_3 target_q_4
1 0 0.086743 -1.085944 1.304110 -0.174707 -0.037001
1 1 0.086743 -1.085944 1.304110 -0.174707 -0.037001
1 2 0.086743 -1.085944 1.304110 -0.174707 -0.037001
1 3 0.086743 -1.085944 1.304110 -0.174707 -0.037001
1 4 0.086743 -1.085944 1.304110 -0.174707 -0.037001
2 0 0.092704 -1.123734 1.368322 -0.206030 -0.006364
2 1 0.092376 -1.121655 1.364788 -0.204306 -0.008050
2 2 0.092057 -1.119634 1.361355 -0.202632 -0.009688
2 3 0.091748 -1.117672 1.358021 -0.201005 -0.011279
3 0 0.092704 -1.123734 1.368322 -0.206030 -0.006364
"""), sep=r"s{2,}", engine="python", index_col=["sample_nr", "event"])
Код
# 1. compute the sizes of each sample_nr
sr_sizes = df.groupby(df.index.get_level_values(0)).size()
# compute max size and #sample_nr
max_size = sr_sizes.max()
n_sample_nrs = len(sr_sizes)
# 2. preallocate the output array and fill
arr = np.zeros((max_size * n_sample_nrs, 5))
idx_lv0 = df.index.get_level_values(0) # get sample_nr
for i in range(n_sample_nrs):
row = i*max_size
arr[row:row sr_sizes.iloc[i], :] =
df[idx_lv0 == sr_sizes.index[i]].values
# 3. convert to dataframe
df_ans = pd.DataFrame(
data=arr,
index=pd.MultiIndex.from_product([sr_sizes.index, range(max_size)]),
columns=df.columns
).rename_axis(df.index.names, axis=0)
Результат
print(df_ans)
target_q_0 target_q_1 target_q_2 target_q_3 target_q_4
sample_nr event
1 0 0.086743 -1.085944 1.304110 -0.174707 -0.037001
1 0.086743 -1.085944 1.304110 -0.174707 -0.037001
2 0.086743 -1.085944 1.304110 -0.174707 -0.037001
3 0.086743 -1.085944 1.304110 -0.174707 -0.037001
4 0.086743 -1.085944 1.304110 -0.174707 -0.037001
2 0 0.092704 -1.123734 1.368322 -0.206030 -0.006364
1 0.092376 -1.121655 1.364788 -0.204306 -0.008050
2 0.092057 -1.119634 1.361355 -0.202632 -0.009688
3 0.091748 -1.117672 1.358021 -0.201005 -0.011279
4 0.000000 0.000000 0.000000 0.000000 0.000000
3 0 0.092704 -1.123734 1.368322 -0.206030 -0.006364
1 0.000000 0.000000 0.000000 0.000000 0.000000
2 0.000000 0.000000 0.000000 0.000000 0.000000
3 0.000000 0.000000 0.000000 0.000000 0.000000
4 0.000000 0.000000 0.000000 0.000000 0.000000
Комментарии:
1. Это действительно намного быстрее! Примерно в 30 раз быстрее, если быть точным, что является значительным улучшением. Единственная проблема, которую я обнаружил, — это скачкообразное использование памяти. Поскольку исходный фрейм данных имеет ~ 6 ГБ и потенциально может увеличиваться, предварительное выделение другого массива сопоставимого размера может быть проблемой. Я не уверен, как Python работает с выделением памяти в этом случае.
2. Вы можете использовать h5py для хранения массива на диске вместо ОЗУ. Или вы можете переделать свою работу в некоторую логику пакетной обработки. Это сильно зависит от вашего реального варианта использования. Возможно, это хорошо описано в другом вопросе.