矢量化 Pandas DataFrame
Vectorization Pandas DataFrame
假设以下简化框架:
我有一个 3D Pandas 参数数据框,由 100 行、4 classes 和每个实例的 4 个特征组成:
iterables = [list(range(100)), [0,1,2,3]]
index = pd.MultiIndex.from_product(iterables, names=['instances', 'classes'])
columns = ['a', 'b', 'c', 'd']
np.random.seed(42)
parameters = pd.DataFrame(np.random.randint(1, 2000, size=(len(index), len(columns))), index=index, columns=columns)
parameters
instances classes a b c d
0 0 1127 1460 861 1295
1 1131 1096 1725 1045
2 1639 122 467 1239
3 331 1483 88 1397
1 0 1124 872 1688 131
... ... ... ... ...
98 3 1321 1750 779 1431
99 0 1793 814 1637 1429
1 1370 1646 420 1206
2 983 825 1025 1855
3 1974 567 371 936
设 df
为每个实例和每个特征(列)的数据框,报告观察到的 class.
np.random.seed(42)
df = pd.DataFrame(np.random.randint(0, 3, size=(100, len(columns))), index=list(range(100)),
columns=columns)
a b c d
0 2 0 2 2
1 0 0 2 1
2 2 2 2 2
3 0 2 1 0
4 1 1 1 1
.. .. .. .. ..
95 1 2 0 1
96 2 1 2 1
97 0 0 1 2
98 0 0 0 1
99 1 2 2 2
我想创建形状为 (100, 4) 的第三个数据框(我们称之为 new_df
),其中包含基于观察到的数据框 parameters
中的参数 class数据框上的 es df
.
例如,在第一列 df
的第一行 (a) 我观察到 class 2,所以我感兴趣的值是第二个 class在 parameters
数据帧的第一个实例中,即 1127 将填充 new df
的第一行和第一列。按照这种方法,“b”列的第一个观察值是 class 0,所以在第一行,new_df
的 b 列我想观察 1460 等等。
通过for循环我可以获得想要的结果:
new_df = pd.DataFrame(0, index=list(range(100)), columns=columns) # initialize the df
for i in range(len(df)):
for c in df.columns:
new_df.iloc[i][c] = parameters.loc[i][c][df.iloc[i][c]]
new_df
a b c d
0 1639 1460 467 1239
1 1124 872 806 344
2 1083 511 1706 1500
3 958 1155 1268 563
4 14 242 777 1370
.. ... ... ... ...
95 1435 1316 1709 755
96 346 712 363 815
97 1234 985 683 1348
98 127 1130 1009 1014
99 1370 825 1025 1855
然而,原始数据集包含数百万行和数百列,无法进行for循环。
有没有办法向量化这样的问题以避免for循环? (至少超过一维)
使用 stack
将两个 DataFrame 重塑为长格式,然后使用 unstack
执行合并和重塑,回到宽格式。有一堆重命名,所以我们可以在合并中引用和对齐列。
(df.rename_axis(index='instances', columns='cols').stack().to_frame('classes')
.merge(parameters.rename_axis(columns='cols').stack().rename('vals'),
on=['instances', 'classes', 'cols'])
.unstack(-1)['vals']
.rename_axis(index=None, columns=None)
)
a b c d
0 1639 1460 467 1239
1 1124 872 806 344
2 1083 511 1706 1500
3 958 1155 1268 563
4 14 242 777 1370
.. ... ... ... ...
95 1435 1316 1709 755
96 346 712 363 815
97 1234 985 683 1348
98 127 1130 1009 1014
99 1370 825 1025 1855
假设以下简化框架:
我有一个 3D Pandas 参数数据框,由 100 行、4 classes 和每个实例的 4 个特征组成:
iterables = [list(range(100)), [0,1,2,3]]
index = pd.MultiIndex.from_product(iterables, names=['instances', 'classes'])
columns = ['a', 'b', 'c', 'd']
np.random.seed(42)
parameters = pd.DataFrame(np.random.randint(1, 2000, size=(len(index), len(columns))), index=index, columns=columns)
parameters
instances classes a b c d
0 0 1127 1460 861 1295
1 1131 1096 1725 1045
2 1639 122 467 1239
3 331 1483 88 1397
1 0 1124 872 1688 131
... ... ... ... ...
98 3 1321 1750 779 1431
99 0 1793 814 1637 1429
1 1370 1646 420 1206
2 983 825 1025 1855
3 1974 567 371 936
设 df
为每个实例和每个特征(列)的数据框,报告观察到的 class.
np.random.seed(42)
df = pd.DataFrame(np.random.randint(0, 3, size=(100, len(columns))), index=list(range(100)),
columns=columns)
a b c d
0 2 0 2 2
1 0 0 2 1
2 2 2 2 2
3 0 2 1 0
4 1 1 1 1
.. .. .. .. ..
95 1 2 0 1
96 2 1 2 1
97 0 0 1 2
98 0 0 0 1
99 1 2 2 2
我想创建形状为 (100, 4) 的第三个数据框(我们称之为 new_df
),其中包含基于观察到的数据框 parameters
中的参数 class数据框上的 es df
.
例如,在第一列 df
的第一行 (a) 我观察到 class 2,所以我感兴趣的值是第二个 class在 parameters
数据帧的第一个实例中,即 1127 将填充 new df
的第一行和第一列。按照这种方法,“b”列的第一个观察值是 class 0,所以在第一行,new_df
的 b 列我想观察 1460 等等。
通过for循环我可以获得想要的结果:
new_df = pd.DataFrame(0, index=list(range(100)), columns=columns) # initialize the df
for i in range(len(df)):
for c in df.columns:
new_df.iloc[i][c] = parameters.loc[i][c][df.iloc[i][c]]
new_df
a b c d
0 1639 1460 467 1239
1 1124 872 806 344
2 1083 511 1706 1500
3 958 1155 1268 563
4 14 242 777 1370
.. ... ... ... ...
95 1435 1316 1709 755
96 346 712 363 815
97 1234 985 683 1348
98 127 1130 1009 1014
99 1370 825 1025 1855
然而,原始数据集包含数百万行和数百列,无法进行for循环。
有没有办法向量化这样的问题以避免for循环? (至少超过一维)
使用 stack
将两个 DataFrame 重塑为长格式,然后使用 unstack
执行合并和重塑,回到宽格式。有一堆重命名,所以我们可以在合并中引用和对齐列。
(df.rename_axis(index='instances', columns='cols').stack().to_frame('classes')
.merge(parameters.rename_axis(columns='cols').stack().rename('vals'),
on=['instances', 'classes', 'cols'])
.unstack(-1)['vals']
.rename_axis(index=None, columns=None)
)
a b c d
0 1639 1460 467 1239
1 1124 872 806 344
2 1083 511 1706 1500
3 958 1155 1268 563
4 14 242 777 1370
.. ... ... ... ...
95 1435 1316 1709 755
96 346 712 363 815
97 1234 985 683 1348
98 127 1130 1009 1014
99 1370 825 1025 1855