Подтвердить что ты не робот

Какой идиоматический способ выполнить операцию агрегации и переименования в pandas

Например, как вы выполняете следующую команду R data.table в pandas:

PATHS[,.( completed=sum(exists), missing=sum(not(exists)), total=.N, 'size (G)'=sum(sizeMB)/1024), by=.(projectPath, pipelineId)]

т.е. группы projectPath и pipelineId, агрегировать некоторые из столбцов используя, возможно, настраиваемые функции, а затем переименуйте полученные столбцы.

Выход должен быть DataFrame без иерархических индексов, например:

                      projectPath pipelineId completed missing size (G)
/data/pnl/projects/TRACTS/pnlpipe          0      2568       0 45.30824
/data/pnl/projects/TRACTS/pnlpipe          1      1299       0 62.69934
4b9b3361

Ответ 1

Вы можете использовать groupby.agg:

df.groupby(['projectPath', 'pipelineId']).agg({
        'exists': {'completed': 'sum', 'missing': lambda x: (~x).sum(), 'total': 'size'},
        'sizeMB': {'size (G)': lambda x: x.sum()/1024}
    })

Пример прогона:

df = pd.DataFrame({
        'projectPath': [1,1,1,1,2,2,2,2],
        'pipelineId': [1,1,2,2,1,1,2,2],
        'exists': [True, False,True,True,False,False,True,False],
        'sizeMB': [120032,12234,223311,3223,11223,33445,3444,23321]
    })

df1 = df.groupby(['projectPath', 'pipelineId']).agg({
        'exists': {'completed': 'sum', 'missing': lambda x: (~x).sum(), 'total': 'size'},
        'sizeMB': {'size (G)': lambda x: x.sum()/1024}
    })
​
df1.columns = df1.columns.droplevel(0)
​
df1.reset_index()

введите описание изображения здесь


Обновление: если вы действительно хотите настроить агрегацию без использования устаревшего вложенного синтаксиса словаря, вы всегда можете использовать groupby.apply и возвращать объект Series из каждой группы:

df.groupby(['projectPath', 'pipelineId']).apply(
    lambda g: pd.Series({
            'completed': g.exists.sum(),
            'missing': (~g.exists).sum(),
            'total': g.exists.size,
            'size (G)': g.sizeMB.sum()/1024 
        })
).reset_index()

введите описание изображения здесь

Ответ 2

Я считаю, что новый 0.20, более "идиоматический" способ похож на этот (где второй слой вложенного словаря в основном заменяется приложенным методом .rename):

...( completed=sum(exists), missing=sum(not(exists)), total=.N, 'size (G)'=sum(sizeMB)/1024), by=.(projectPath, pipelineId)]... в R, становится

EDIT: используйте as_index=False в pd.DataFrame.groupby(), чтобы предотвратить MultiIndex в финальном df

df.groupby(['projectPath', 'pipelineId'], as_index=False).agg({
    'exists': 'sum', 
    'pipelineId': 'count', 
    'sizeMB': lambda s: s.sum() / 1024
}).rename(columns={'exists': 'completed', 
                   'pipelineId': 'total',
                   'sizeMB': 'size (G)'})

И тогда я могу просто добавить еще одну строку для инверсии 'exists' → 'missing':

df['missing'] = df.total - df.completed

В качестве примера в тесте ноутбука Jupyter ниже показано дерево каталогов из 46 общих путей конвейера, импортированных pd.read_csv() в DataFrame Pandas, и я немного изменил рассматриваемый пример, чтобы иметь случайные данные в форме ДНК-цепочек между 1000-100 тыс. нуклеотидных оснований, вместо создания файлов размера Mb. Недискретные гигабазы ​​по-прежнему вычисляются, используя NumPy np.mean() для агрегированного объекта pd.Series, доступного в вызове df.agg для разработки процесса, но lambda s: s.mean() - это более простой способ сделать это.

например,

df_paths.groupby(['TRACT', 'pipelineId']).agg({
    'mean_len(project)' : 'sum',
    'len(seq)' : lambda agg_s: np.mean(agg_s.values) / 1e9
}).rename(columns={'len(seq)': 'Gb',
                   'mean_len(project)': 'TRACT_sum'})

где "ТРАКТ" был категорией более высокого уровня к "pipId" в дереве, таким образом, что в этом примере вы можете увидеть 46 общих уникальных трубопроводов - 2 слоя "TRACT" AB/AC x 6 "pipId" / "project" x 4 двоичных комбинаций 00, 01, 10, 11 (минус 2 проекта, которые GNU параллельно вносит в третий верхний регистр, см. ниже). Таким образом, в новой статистике статистика преобразует среднее значение уровня проекта в суммы всех соответствующих проектов agg'd per-TRACT.

введите описание изображения здесь

df_paths = pd.read_csv('./data/paths.txt', header=None, names=['projectPath'])
# df_paths['projectPath'] = 
df_paths['pipelineId'] = df_paths.projectPath.apply(
    lambda s: ''.join(s.split('/')[1:5])[:-3])
df_paths['TRACT'] = df_paths.pipelineId.apply(lambda s: s[:2])
df_paths['rand_DNA'] = [
    ''.join(random.choices(['A', 'C', 'T', 'G'], 
                           k=random.randint(1e3, 1e5)))
    for _ in range(df_paths.shape[0])
]
df_paths['len(seq)'] = df_paths.rand_DNA.apply(len)
df_paths['mean_len(project)'] = df_paths.pipelineId.apply(
    lambda pjct: df_paths.groupby('pipelineId')['len(seq)'].mean()[pjct])
df_paths

введите описание изображения здесь