我有很多数据需要在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
中,其中有多个索引看起来像下面的表示,或者类似。 (为简单起见,我只使用了task1
或seg1
而不是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}'
)应该没有问题。
答案 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)
重新生成数据框创建代码
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'
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