将dict构造函数转换为Pandas MultiIndex数据帧

时间:2018-03-06 13:12:06

标签: python python-3.x pandas dictionary dataframe

我有很多数据需要在Pandas数据帧中构建。但是,我需要一个多索引格式。 Pandas MultiIndex功能一直困扰着我,而且这次我无法理解它。

我构建了结构,因为我希望它作为一个字典,但因为我的实际数据要大得多,所以我想用Pandas代替。以下代码是dict变体。请注意,原始数据包含更多标签和更多行。

这个想法是原始数据包含索引Task_n的任务行,该任务由索引为Participant_n的参与者执行。每个都是一个细分受众群。即使原始数据没有这种区别,我想将其添加到我的数据帧中。换句话说:

Participant_n | Task_n | val | dur
----------------------------------
            1 |      1 |  12 |   2
            1 |      1 |   3 |   4
            1 |      1 |   4 |  12
            1 |      2 |  11 |  11
            1 |      2 |  34 |   4

上面的示例包含一个参与者,两个任务,分别包含三个两个段(行)

在Python中,使用dict结构,如下所示:

import pandas as pd

cols = ['Participant_n', 'Task_n', 'val', 'dur']

data = [[1,1,25,83],
        [1,1,4,68],
        [1,1,9,987],
        [1,2,98,98],
        [1,2,84,4],
        [2,1,9,21],
        [2,2,15,6],
        [2,2,185,6],
        [2,2,18,4],
        [2,3,8,12],
        [3,1,7,78],
        [3,1,12,88],
        [3,2,12,48]]

d = pd.DataFrame(data, columns=cols)

part_d = {}
for row in d.itertuples():
    participant_n = row.Participant_n
    participant = "participant" + str(participant_n)
    task = "task" + str(row.Task_n)

    if participant in part_d:
        part_d[participant]['all_sum']['val'] += int(row.val)
        part_d[participant]['all_sum']['dur'] += int(row.dur)
    else:
        part_d[participant] = {
            'prof': 0 if participant_n < 20 else 1,
            'all_sum': {
                'val': int(row.val),
                'dur': int(row.dur),
            }
        }

    if task in part_d[participant]:
        # Get already existing keys
        k = list(part_d[participant][task].keys())

        k_int = []
        # Only get the ints (i.e. not all_sum etc.)
        for n in k:
            # Get digit from e.g. seg1
            n = n[3:]
            try:
                k_int.append(int(n))
            except ValueError:
                pass

        # Increment max by 1
        i = max(k_int) + 1
        part_d[participant][task][f"seg{i}"] = {
            'val': int(row.val),
            'dur': int(row.dur),
        }
        part_d[participant][task]['task_sum']['val'] += int(row.val)
        part_d[participant][task]['task_sum']['dur'] += int(row.dur)
    else:
        part_d[participant][task] = {
            'seg1': {
                'val': int(row.val),
                'dur': int(row.dur),
            },
            'task_sum': {
                'val': int(row.val),
                'dur': int(row.dur),
            }
        }

print(part_d)

在最后的结果中,我有一些额外的变量,例如:task_sum(参与者任务的总和),all_sum(所有参与者的动作的总和),以及prof,它是一个任意的布尔值旗。生成的dict看起来像这样(没有美化以节省空间。如果你想检查,在文本编辑器中打开JSON或Python dict并美化):

{'participant1': {'prof': 0, 'all_sum': {'val': 220, 'dur': 1240}, 'task1': {'seg1': {'val': 25, 'dur': 83}, 'task_sum': {'val': 38, 'dur': 1138}, 'seg2': {'val': 4, 'dur': 68}, 'seg3': {'val': 9, 'dur': 987}}, 'task2': {'seg1': {'val': 98, 'dur': 98}, 'task_sum': {'val': 182, 'dur': 102}, 'seg2': {'val': 84, 'dur': 4}}}, 'participant2': {'prof': 0, 'all_sum': {'val': 235, 'dur': 49}, 'task1': {'seg1': {'val': 9, 'dur': 21}, 'task_sum': {'val': 9, 'dur': 21}}, 'task2': {'seg1': {'val': 15, 'dur': 6}, 'task_sum': {'val': 218, 'dur': 16}, 'seg2': {'val': 185, 'dur': 6}, 'seg3': {'val': 18, 'dur': 4}}, 'task3': {'seg1': {'val': 8, 'dur': 12}, 'task_sum': {'val': 8, 'dur': 12}}}, 'participant3': {'prof': 0, 'all_sum': {'val': 31, 'dur': 214}, 'task1': {'seg1': {'val': 7, 'dur': 78}, 'task_sum': {'val': 19, 'dur': 166}, 'seg2': {'val': 12, 'dur': 88}}, 'task2': {'seg1': {'val': 12, 'dur': 48}, 'task_sum': {'val': 12, 'dur': 48}}}}

而不是字典,我希望这最终会出现在pd.DataFrame中,其中有多个索引看起来像下面的表示,或者类似。 (为简单起见,我只使用了task1seg1而不是Participant Prof all_sum Task Task_sum Seg val dur val dur val dur ==================================================================== participant1 0 220 1240 1 38 1138 1 25 83 2 4 68 3 9 987 2 182 102 1 98 98 2 84 4 -------------------------------------------------------------------- participant2 0 235 49 1 9 21 1 9 21 2 218 16 1 15 6 2 185 6 3 18 4 3 8 12 1 8 12 -------------------------------------------------------------------- participant3 0 31 214 1 19 166 1 7 78 2 12 88 2 12 48 1 12 48 。)

Participant  Prof  all_sum_val  all_sum_dur  Task  Task_sum_val  Task_sum_dur  Seg   

这是Pandas可能的结构吗?如果没有,哪些合理的选择是什么?

我必须再次强调,实际上有更多的数据,可能还有更多的子级别。因此,解决方案必须灵活,高效。如果它使事情变得更简单,我愿意在一个轴上只有多索引,并将标题更改为:

dict

我遇到的主要问题是,如果我事先不知道尺寸,我不明白如何构建多索引df。我事先并不知道会有多少任务或细分。所以我很确定我可以保留我最初的prof === 0方法的循环结构,我想我必须将/ concat追加到一个初始的空DataFrame,但问题是结构必须看起来像什么。它不能是一个简单的系列,因为它不会考虑多索引。怎么样?

对于那些已经阅读过这篇文章且想要尝试这一点的人,我认为我的原始代码可以在大多数情况下重复使用(循环和变量赋值),但它不是一个字典,而是必须是DataFrame的访问者。这是一个导入方面:使用getter / setter可以轻松读取数据,就像常规的DataFrame一样。例如。应该很容易获得参与者2,任务2,段2等的持续时间值。但是,获取数据的子集(例如,perl -MTime::HiRes=time -E 'while(1){my $now=sprintf("%.9f",time); die if($now==$last);$last=$now}' )应该没有问题。

2 个答案:

答案 0 :(得分:6)

我唯一的建议就是摆脱你所有的字典。所有这些代码都可以在Pandas中重写,而不需要太多努力。这可能会加快转型过程,但需要一些时间。为了帮助您完成此过程,我重写了您提供的部分。其余的由你决定。

import pandas as pd

cols = ['Participant_n', 'Task_n', 'val', 'dur']

data = [[1,1,25,83],
        [1,1,4,68],
        [1,1,9,987],
        [1,2,98,98],
        [1,2,84,4],
        [2,1,9,21],
        [2,2,15,6],
        [2,2,185,6],
        [2,2,18,4],
        [2,3,8,12],
        [3,1,7,78],
        [3,1,12,88],
        [3,2,12,48]]

df = pd.DataFrame(data, columns=cols)
df["Task Sum val"] = df.groupby(["Participant_n","Task_n"])["val"].transform("sum")
df["Task Sum dur"] = df.groupby(["Participant_n","Task_n"])["dur"].transform("sum")
df["seg"] =df.groupby(["Participant_n","Task_n"]).cumcount() + 1
df["All Sum val"] = df.groupby("Participant_n")["val"].transform("sum")
df["All Sum dur"] = df.groupby("Participant_n")["dur"].transform("sum")
df = df.set_index(["Participant_n","All Sum val","All Sum dur","Task_n","Task Sum val","Task Sum dur"])[["seg","val","dur"]]
df = df.sort_index()
df

输出

                                                                        seg  val  dur
Participant_n All Sum val All Sum dur Task_n Task Sum val Task Sum dur               
1             220         1240        1      38           1138            1   25   83
                                                          1138            2    4   68
                                                          1138            3    9  987
                                      2      182          102             1   98   98
                                                          102             2   84    4
2             235         49          1      9            21              1    9   21
                                      2      218          16              1   15    6
                                                          16              2  185    6
                                                          16              3   18    4
                                      3      8            12              1    8   12
3             31          214         1      19           166             1    7   78
                                                          166             2   12   88
                                      2      12           48              1   12   48

尝试运行此代码并让我知道您的想法。评论任何问题。

答案 1 :(得分:2)

我遇到了类似的数据表示问题,并为groupby提供了以下辅助函数和小计。

通过此过程,可以按列生成任意数量的小计,但输出数据的格式不同。而不是将小计放在各自的列中,每个小计都会在数据框中添加一个额外的行。

用于交互式数据探索&amp;分析,我发现这非常有用,因为只需要几行代码即可获得小计

def get_subtotals(frame, columns, aggvalues, subtotal_level):

    if subtotal_level == 0:
        return frame.groupby(columns, as_index=False).agg(aggvalues)

    elif subtotal_level == len(columns):
        return pd.DataFrame(frame.agg(aggvalues)).transpose().assign(
            **{c: np.nan  for i, c in enumerate(columns)}
        )

    return frame.groupby(
        columns[:subtotal_level],
        as_index=False
    ).agg(aggvalues).assign(
        **{c: np.nan for i, c in enumerate(columns[subtotal_level:])}
    )

def groupby_with_subtotals(frame, columns, aggvalues, grand_totals=False, totals_position='last'):
    gt = 1 if grand_totals else 0
    out = pd.concat(   
        [get_subtotals(df, columns, aggvalues, i)
         for i in range(len(columns)+gt)]
     ).sort_values(columns, na_position=totals_position)
    out[columns] = out[columns].fillna('total')
    return out.set_index(columns)

Gabriel A's answer

重新生成数据框创建代码
cols = ['Participant_n', 'Task_n', 'val', 'dur']

data = [[1,1,25,83],
        [1,1,4,68],
        [1,1,9,987],
        [1,2,98,98],
        [1,2,84,4],
        [2,1,9,21],
        [2,2,15,6],
        [2,2,185,6],
        [2,2,18,4],
        [2,3,8,12],
        [3,1,7,78],
        [3,1,12,88],
        [3,2,12,48]]

df = pd.DataFrame(data, columns=cols)

首先需要添加seg

df['seg'] = df.groupby(['Participant_n', 'Task_n']).cumcount() + 1

然后我们可以像这样使用groupby_with_subtotals。另外,请注意,您可以将小计放在顶部,并通过传入grand_totals=True, totals_position='first'

包含grand_totals
groupby_columns = ['Participant_n', 'Task_n', 'seg']
groupby_aggs = {'val': 'sum', 'dur': 'sum'}
aggdf = groupby_with_subtotals(df, groupby_columns, groupby_aggs)
aggdf
# outputs

                             dur  val
Participant_n Task_n seg
1             1.0    1.0      83   25
                     2.0      68    4
                     3.0     987    9
                     total  1138   38
              2.0    1.0      98   98
                     2.0       4   84
                     total   102  182
              total  total  1240  220
2             1.0    1.0      21    9
                     total    21    9
              2.0    1.0       6   15
                     2.0       6  185
                     3.0       4   18
                     total    16  218
              3.0    1.0      12    8
                     total    12    8
              total  total    49  235
3             1.0    1.0      78    7
                     2.0      88   12
                     total   166   19
              2.0    1.0      48   12
                     total    48   12
              total  total   214   31

此处,小计行标有total,最左边total表示小计等级。

创建聚合数据框后,可以使用loc访问小计。例如:

aggdf.loc[1,'total','total']
# outputs:
dur    1240
val     220
Name: (1, total, total), dtype: int64