Python 通过避免循环对pandas中的代码进行矢量化

Python 通过避免循环对pandas中的代码进行矢量化,python,python-3.x,pandas,dataframe,Python,Python 3.x,Pandas,Dataframe,考虑以下因素: In [448]: complex_dataframe = pd.DataFrame({'stat_A': [120, 121, 122, 123], ...: 'group_id_A': [1, 1, 1, 2], ...: 'level_A': [1, 2, 2, 1], ...:

考虑以下因素:

In [448]: complex_dataframe = pd.DataFrame({'stat_A': [120, 121, 122, 123],
     ...:                                       'group_id_A': [1, 1, 1, 2],
     ...:                                       'level_A': [1, 2, 2, 1],
     ...:                                       'stat_B': [220, 221, 222, 223],
     ...:                                       'group_id_B': [1, 1, 1, 2],
     ...:                                       'level_B': [1, 1, 2, 2],
     ...:                                       'stat_C': ['aa', 'ab', 'aa', 'ab'],
     ...:                                       'measure_avg_A': [10.5, 11, 20, 12],
     ...:                                       'measure_sum_B': [10, 20, 30, 40]}
     ...:                                      )

In [449]: complex_dataframe
Out[449]: 
   stat_A  group_id_A  level_A  stat_B  group_id_B  level_B stat_C  measure_avg_A  measure_sum_B
0     120           1        1     220           1        1     aa           10.5             10
1     121           1        2     221           1        1     ab           11.0             20
2     122           1        2     222           1        2     aa           20.0             30
3     123           2        1     223           2        2     ab           12.0             40
这里有三列的变量:
stat
group\u id
level
复杂的
列,只有
stat
的变量是简单的列

因此,上面的
A
B
列是复杂的列。列
C
是一个简单的列,以
measure\开头的列仅仅是值

用例是:

我需要对所有
group\u id\u
列和简单列进行分组。在上述情况下,groupby on:
组id\u A
组id\u B
统计C

预期产出:

其中
measure_A
列如下所示:

我已经使用多个循环对此进行了编码

from collections import Counter

cols_without_measures = complex_dataframe.loc[:, ~complex_dataframe.columns.str.startswith("measure_")].columns.tolist()
cols_without_measures = [i.split('_')[-1] for i in cols_without_measures]
counter = Counter(cols_without_measures)

complex_cols = [k for k, v in counter.items() if v == 3]
simple_cols = list(set(list(counter.keys())).symmetric_difference(set(complex_cols)))
grouped_cols = ['group_id_' + i for i in complex_cols] + ['stat_' + i for i in simple_cols]

grp = self.df_in.groupby(grouped_cols)
complex_df = pd.DataFrame()

for k, v in grp:
    temp = v.loc[:, ~v.columns.isin(grouped_cols)]
    stat_df = temp.loc[:, ~temp.columns.str.startswith('measure_')]
    measure_df = temp.filter(like='measure_', axis=1)
    out_df = v[grouped_cols].head(1)
    for i in measure_df.columns:
        m = stat_df.copy()
        m[i] = measure_df[i]
        out_df[i] = [m.to_dict()]
    complex_df = complex_df.append(out_df)

有没有更好的办法解决这个问题?也许可以以某种方式将其矢量化。

如果您愿意通过更具逻辑性的索引访问分组的数据帧,您可以这样做:

df_grouped = complex_dataframe.groupby(['group_id_A', 'group_id_B', 'stat_C'])
然后,您将通过分组所依据的值的元组访问每个组:

df_grouped.get_group((1, 1, 'ab'))
为您提供数据帧

        stat_A  group_id_A  level_A stat_B  group_id_B  level_B stat_C measure_avg_A    measure_sum_B
    1   121     1           2       21      1           1       ab     11.0             20
您可以使用以下命令遍历组:

for key, item in df_grouped:
    print(key, "\n")
    print(df_grouped.get_group(key), "\n\n")

您可以在pandas中使用groupby来实现这一点,但它使用的是apply和lambda。这不是一个完全矢量化的解决方案

df_complex = complex_dataframe.groupby(['group_id_A','group_id_B','stat_C']).apply(
    lambda x: pd.Series({
        'measure_avg_A': x[['stat_A','level_A','stat_B','level_B','measure_avg_A']].to_dict(),
        'measure_sum_B': x[['stat_A','level_A','stat_B','level_B','measure_sum_B']].to_dict()
    })).reset_index()
然后,您可以根据需要查询数据帧

pd.DataFrame(df_complex.at[0, 'measure_avg_A'])
输出

   stat_A  level_A  stat_B  level_B  measure_avg_A
0     120        1     220        1           10.5
2     122        2     222        2           20.0

不确定这是否更好,但您可以尝试让我知道:

measure_cols = [*complex_dataframe.columns[complex_dataframe.columns
                                           .str.contains("measure")]]
u = complex_dataframe.set_index(grouped_cols)

final=pd.concat([u[u.columns.difference(measure_cols,sort=False).union([i],sort=False)]
        .groupby(grouped_cols).apply(lambda x: x.reset_index(drop=True).to_dict())
        .rename(i)   for i in measure_cols],axis=1).reset_index()

与一起使用,并通过轴1连接

def d(x):
    return x.to_dict()
g = df.groupby(['group_id_A','group_id_B','stat_C'])
one = g[['stat_A','level_A','stat_B','level_B','measure_avg_A']].apply(d)
two = g[['stat_A','level_A','stat_B','level_B','measure_sum_B']].apply(d)

out = pd.concat([one, two], axis=1)
out.columns = ['measure_avg_A', 'measure_sum_B']
从anky那里借用一些代码:p

display = print
final = out 

                                                                  measure_avg_A                                      measure_sum_B
group_id_A group_id_B stat_C                                                                                                      
1          1          aa      {'stat_A': {0: 120, 2: 122}, 'level_A': {0: 1,...  {'stat_A': {0: 120, 2: 122}, 'level_A': {0: 1,...
                      ab      {'stat_A': {1: 121}, 'level_A': {1: 2}, 'stat_...  {'stat_A': {1: 121}, 'level_A': {1: 2}, 'stat_...
2          2          ab      {'stat_A': {3: 123}, 'level_A': {3: 1}, 'stat_...  {'stat_A': {3: 123}, 'level_A': {3: 1}, 'stat_...

   stat_A  level_A  stat_B  level_B  measure_avg_A
0     120        1     220        1           10.5
2     122        2     222        2           20.0

   stat_A  level_A  stat_B  level_B  measure_avg_A
1     121        2     221        1           11.0

   stat_A  level_A  stat_B  level_B  measure_sum_B
0     120        1     220        1             10
2     122        2     222        2             30

   stat_A  level_A  stat_B  level_B  measure_sum_B
1     121        2     221        1             20

虽然这是一个可能的解决方案,但我建议重新考虑您的数据布局。一个更简单的数据框可能会归档相同的功能。这看起来不错,唯一的一点是,
pd.Series
中使用的列可以是多个。我必须循环它们吗?如果您有许多列要创建,我会在pd.Series中使用
字典理解
。但请注意,这很可能不是一个好的解决方案。除非您有必要在一个数据帧内创建多个数据帧/dict。我理解这一点。请您在回答中使用
apply
语句中的
dict comprehension
提供解决方案。对不起,现在没有时间。也许这是另一个问题的好话题?解决方案运行良好。看起来完全避免
for
循环是不可能的。这里使用的函数太多,有点复杂了。@MayankPorwal谢谢你的确认,我现在无法理解无循环解决方案。:/对不起@耶斯雷尔你能看看这个问题吗?
display = print
final = out 

                                                                  measure_avg_A                                      measure_sum_B
group_id_A group_id_B stat_C                                                                                                      
1          1          aa      {'stat_A': {0: 120, 2: 122}, 'level_A': {0: 1,...  {'stat_A': {0: 120, 2: 122}, 'level_A': {0: 1,...
                      ab      {'stat_A': {1: 121}, 'level_A': {1: 2}, 'stat_...  {'stat_A': {1: 121}, 'level_A': {1: 2}, 'stat_...
2          2          ab      {'stat_A': {3: 123}, 'level_A': {3: 1}, 'stat_...  {'stat_A': {3: 123}, 'level_A': {3: 1}, 'stat_...

   stat_A  level_A  stat_B  level_B  measure_avg_A
0     120        1     220        1           10.5
2     122        2     222        2           20.0

   stat_A  level_A  stat_B  level_B  measure_avg_A
1     121        2     221        1           11.0

   stat_A  level_A  stat_B  level_B  measure_sum_B
0     120        1     220        1             10
2     122        2     222        2             30

   stat_A  level_A  stat_B  level_B  measure_sum_B
1     121        2     221        1             20