清理一列字符串并添加新列的更有效方法
More efficient way to clean a column of strings and add a new column
我有一个包含列 ['metric_type', 'metric_value']
的数据框 df
。对于每一行,我想确保我有一个名称等于 'metric_type'
的列,该列的值等于 'metric_value'
.
我的一个问题是 'metric_type'
有我想删除的虚假空格。
考虑数据框 df
:
df = pd.DataFrame([
['a ', 1],
[' b', 2],
[' c ', 3]
], columns=['metric_type', 'metric_value'])
print(df)
metric_type metric_value
0 a 1
1 b 2
2 c 3
请注意 'metric_type'
的每个值在不同的地方都有空格。
我创建了一个函数来使用 apply
,但它需要很长时间。
def assign_metric_vals(row):
row[row['metric_type'].replace(" ", "")] = row['metric_value']
return row
当我使用它时,我得到这个:
a b c metric_type metric_value
0 1.0000 nan nan a 1
1 nan 2.00 nan b 2
2 nan nan 3.00 c 3
是否有更好的(阅读,"faster")方法来完成同样的任务?
使用 metric_type
设置索引并取消堆叠会更好。
df.set_index(df.metric_type.str.replace(' ', ''), append=True).metric_value.unstack()
演示
df = pd.DataFrame([
['a ', 1],
[' b', 2],
[' c ', 3]
], columns=['metric_type', 'metric_value'])
print(df)
metric_type metric_value
0 a 1
1 b 2
2 c 3
print(df.apply(assign_metric_vals, 1))
a b c metric_type metric_value
0 1.0000 nan nan a 1
1 nan 2.00 nan b 2
2 nan nan 3.00 c 3
或者我的方式
idx = df.metric_type.str.replace(' ', '')
d1 = df.set_index(idx, append=True).metric_value.unstack()
print(pd.concat([d1, df], axis=1))
a b c metric_type metric_value
0 1.0000 nan nan a 1
1 nan 2.00 nan b 2
2 nan nan 3.00 c 3
时间
使用更大的 df
df1 = pd.concat([df] * 30000, ignore_index=True)
%%timeit
idx = df1.metric_type.str.replace(' ', '')
d1 = df1.set_index(idx, append=True).metric_value.unstack()
pd.concat([d1, df1], axis=1)
10 loops, best of 3: 77.3 ms per loop
%%timeit
df1.apply(assign_metric_vals, 1)
1 loop, best of 3: 57.4 s per loop
这是一个比@piRSquared 快大约 20% 并给出相同答案的替代方案。我不会建议它是好是坏(一般来说),但是赏金是在那个答案被接受后发布的,所以我会把它作为一个额外的选项提供。
%%timeit
idx = df1.metric_type.str.replace(' ', '')
d1 = df1.set_index(idx, append=True).metric_value.unstack()
result1 = pd.concat([d1, df1], axis=1)
10 loops, best of 3: 97.6 ms per loop
%%timeit
df1.metric_type = df1.metric_type.str.strip()
d1 = df1.pivot(columns='metric_type', values='metric_value')
result2 = pd.concat([d1, df1], axis=1)
10 loops, best of 3: 77.2 ms per loop
大约 1/3 的速度提升来自使用 strip
而不是 replace
,2/3 来自使用 pivot
而不是 unstack
。 (concat
步骤是一样的,而且非常快)。
看看最终数据帧的创建方式,与目前提到的其他方法相比,就整体性能而言,字符串列的单热编码确实不是一个坏主意.
步骤:
在 metric_type
系列上使用 pd.get_dummies
,从分类变量创建虚拟变量。这部分加上str.strip
是最耗时的。
我们可以在计算 get_dummies
部分而不是直接在系列对象上剥离 leading/trailing 空白字符,因为某些分类变量很有可能确实在系列中重复出现,稍后将在虚拟创建期间共享同一列。重复的变量越多,过滤掉这些额外空间所花费的时间就越少。仅对获得的虚拟变量DF
的列执行str.strip
。这种方法可以节省大量时间。
- 对获得的这些列进行排序,以便它们按字典顺序排序,并且重复的列(如果存在)将彼此相邻放置。允许根据这些列组合修改
DF
。
- 使用
np.unique
和 return_index=True
参数来提取存在的唯一列及其对应的索引。
- 我们需要找到一种方法将相同的列组合成一个单一的有益列。为此,我们可以使用
np.add.reduceat
,它的工作方式类似于 groupby
操作(等效 - df.groupby(df.columns.tolist(), axis=1).sum()
),但它的特点是非常快。要配对的索引由 np.unique
的 idx
提供。值的减少发生在这些切片上,并且它们的 运行 总和是跨列计算的 (axis=1
)。
- 返回的
dtype
是 bool
,这有助于我们使用 np.where
,因为它的功能类似于布尔掩码,其中 1's/0 被映射到 True
/False
分别。然后这些 1 由 metric_value
系列中的值填充,0 由 NaN
. 中的值填充
- 我们的
DF
现已准备就绪,需要将其与原始起始 DF
按列连接,从而产生最终清理后的数据帧。
解法:
def dummies_strip_concat(df):
one_hot_enc = pd.get_dummies(df.metric_type)
one_hot_enc.columns = one_hot_enc.columns.str.strip()
one_hot_enc.sortlevel(axis=1, inplace=True)
a, idx = np.unique(one_hot_enc.columns.values, return_index=True)
out = np.where(np.add.reduceat(one_hot_enc.values, idx, axis=1, dtype=np.bool),
df.metric_value.values[:, None],
np.nan)
return (pd.concat([pd.DataFrame(out, df.index, a), df], axis=1))
时间:
def pir(df):
idx = df.metric_type.str.replace(' ', '')
d1 = df.set_index(idx, append=True).metric_value.unstack()
return pd.concat([d1, df], axis=1)
def johne(df):
df.metric_type = df.metric_type.str.strip()
d1 = df.pivot(columns='metric_type', values='metric_value')
return pd.concat([d1, df], axis=1)
对于包含几千行的 DF
与 OP 的想法相当:
df1 = pd.concat([df] * 30000, ignore_index=True)
df1.shape
(90000, 2)
# Check whether they produce the same outcome
dummies_strip_concat(df1).equals(pir(df1))
True
%timeit pir(df1)
10 loops, best of 3: 97.5 ms per loop
%timeit johne(df1)
10 loops, best of 3: 76.5 ms per loop
%timeit dummies_strip_concat(df1)
100 loops, best of 3: 13.2 ms per loop
我有一个包含列 ['metric_type', 'metric_value']
的数据框 df
。对于每一行,我想确保我有一个名称等于 'metric_type'
的列,该列的值等于 'metric_value'
.
我的一个问题是 'metric_type'
有我想删除的虚假空格。
考虑数据框 df
:
df = pd.DataFrame([
['a ', 1],
[' b', 2],
[' c ', 3]
], columns=['metric_type', 'metric_value'])
print(df)
metric_type metric_value
0 a 1
1 b 2
2 c 3
请注意 'metric_type'
的每个值在不同的地方都有空格。
我创建了一个函数来使用 apply
,但它需要很长时间。
def assign_metric_vals(row):
row[row['metric_type'].replace(" ", "")] = row['metric_value']
return row
当我使用它时,我得到这个:
a b c metric_type metric_value
0 1.0000 nan nan a 1
1 nan 2.00 nan b 2
2 nan nan 3.00 c 3
是否有更好的(阅读,"faster")方法来完成同样的任务?
使用 metric_type
设置索引并取消堆叠会更好。
df.set_index(df.metric_type.str.replace(' ', ''), append=True).metric_value.unstack()
演示
df = pd.DataFrame([
['a ', 1],
[' b', 2],
[' c ', 3]
], columns=['metric_type', 'metric_value'])
print(df)
metric_type metric_value
0 a 1
1 b 2
2 c 3
print(df.apply(assign_metric_vals, 1))
a b c metric_type metric_value
0 1.0000 nan nan a 1
1 nan 2.00 nan b 2
2 nan nan 3.00 c 3
或者我的方式
idx = df.metric_type.str.replace(' ', '')
d1 = df.set_index(idx, append=True).metric_value.unstack()
print(pd.concat([d1, df], axis=1))
a b c metric_type metric_value
0 1.0000 nan nan a 1
1 nan 2.00 nan b 2
2 nan nan 3.00 c 3
时间
使用更大的 df
df1 = pd.concat([df] * 30000, ignore_index=True)
%%timeit
idx = df1.metric_type.str.replace(' ', '')
d1 = df1.set_index(idx, append=True).metric_value.unstack()
pd.concat([d1, df1], axis=1)
10 loops, best of 3: 77.3 ms per loop
%%timeit
df1.apply(assign_metric_vals, 1)
1 loop, best of 3: 57.4 s per loop
这是一个比@piRSquared 快大约 20% 并给出相同答案的替代方案。我不会建议它是好是坏(一般来说),但是赏金是在那个答案被接受后发布的,所以我会把它作为一个额外的选项提供。
%%timeit
idx = df1.metric_type.str.replace(' ', '')
d1 = df1.set_index(idx, append=True).metric_value.unstack()
result1 = pd.concat([d1, df1], axis=1)
10 loops, best of 3: 97.6 ms per loop
%%timeit
df1.metric_type = df1.metric_type.str.strip()
d1 = df1.pivot(columns='metric_type', values='metric_value')
result2 = pd.concat([d1, df1], axis=1)
10 loops, best of 3: 77.2 ms per loop
大约 1/3 的速度提升来自使用 strip
而不是 replace
,2/3 来自使用 pivot
而不是 unstack
。 (concat
步骤是一样的,而且非常快)。
看看最终数据帧的创建方式,与目前提到的其他方法相比,就整体性能而言,字符串列的单热编码确实不是一个坏主意.
步骤:
在
metric_type
系列上使用pd.get_dummies
,从分类变量创建虚拟变量。这部分加上str.strip
是最耗时的。我们可以在计算
get_dummies
部分而不是直接在系列对象上剥离 leading/trailing 空白字符,因为某些分类变量很有可能确实在系列中重复出现,稍后将在虚拟创建期间共享同一列。重复的变量越多,过滤掉这些额外空间所花费的时间就越少。仅对获得的虚拟变量DF
的列执行str.strip
。这种方法可以节省大量时间。- 对获得的这些列进行排序,以便它们按字典顺序排序,并且重复的列(如果存在)将彼此相邻放置。允许根据这些列组合修改
DF
。 - 使用
np.unique
和return_index=True
参数来提取存在的唯一列及其对应的索引。 - 我们需要找到一种方法将相同的列组合成一个单一的有益列。为此,我们可以使用
np.add.reduceat
,它的工作方式类似于groupby
操作(等效 -df.groupby(df.columns.tolist(), axis=1).sum()
),但它的特点是非常快。要配对的索引由np.unique
的idx
提供。值的减少发生在这些切片上,并且它们的 运行 总和是跨列计算的 (axis=1
)。 - 返回的
dtype
是bool
,这有助于我们使用np.where
,因为它的功能类似于布尔掩码,其中 1's/0 被映射到True
/False
分别。然后这些 1 由metric_value
系列中的值填充,0 由NaN
. 中的值填充
- 我们的
DF
现已准备就绪,需要将其与原始起始DF
按列连接,从而产生最终清理后的数据帧。
解法:
def dummies_strip_concat(df):
one_hot_enc = pd.get_dummies(df.metric_type)
one_hot_enc.columns = one_hot_enc.columns.str.strip()
one_hot_enc.sortlevel(axis=1, inplace=True)
a, idx = np.unique(one_hot_enc.columns.values, return_index=True)
out = np.where(np.add.reduceat(one_hot_enc.values, idx, axis=1, dtype=np.bool),
df.metric_value.values[:, None],
np.nan)
return (pd.concat([pd.DataFrame(out, df.index, a), df], axis=1))
时间:
def pir(df):
idx = df.metric_type.str.replace(' ', '')
d1 = df.set_index(idx, append=True).metric_value.unstack()
return pd.concat([d1, df], axis=1)
def johne(df):
df.metric_type = df.metric_type.str.strip()
d1 = df.pivot(columns='metric_type', values='metric_value')
return pd.concat([d1, df], axis=1)
对于包含几千行的 DF
与 OP 的想法相当:
df1 = pd.concat([df] * 30000, ignore_index=True)
df1.shape
(90000, 2)
# Check whether they produce the same outcome
dummies_strip_concat(df1).equals(pir(df1))
True
%timeit pir(df1)
10 loops, best of 3: 97.5 ms per loop
%timeit johne(df1)
10 loops, best of 3: 76.5 ms per loop
%timeit dummies_strip_concat(df1)
100 loops, best of 3: 13.2 ms per loop