优化 Pandas 中的值插值
Optimizing interpolation of values in Pandas
我一直在努力解决 Pandas 的优化问题。
我开发了一个脚本来对相对较小的 DataFrame 的每一行应用计算(大约几千行,几十列)。
我严重依赖 apply() 函数,这在大多数情况下显然是一个糟糕的选择。
经过一轮优化,我只有一个方法需要时间,我还没有找到一个简单的解决方案:
基本上我的数据框包含一个视频观看统计列表,其中包含每个四分位数观看视频的人数(有多少人观看了 0%、25%、50% 等),例如:
video_name
video_length
video_0
video_25
video_50
video_75
video_100
video_1
6
1000
500
300
250
5
video_2
30
1000
500
300
250
5
我正在尝试对统计数据进行插值,以便能够回答“如果视频持续 X 秒,有多少人会观看视频的每个四分位数”
现在我的函数接受数据帧和一个“new_length”参数,并在每一行调用 apply()。
处理每一行的函数计算每个四分位数的时间标记(因此 0、7.5、15、22.5 和 30 用于 30 秒的视频),以及给定新长度的每个四分位数的时间标记(以便减少30 秒视频到 6 秒,新的时间标记将是 0、1.5、3、4.5 和 6)。
我构建了一个数据框,其中包含时间标记作为索引,统计数据作为第一列中的值:
index (time marks)
view_stats
0
1000
7.5
500
15
300
22.5
250
30
5
1.5
NaN
3
NaN
4.5
NaN
然后我调用 DataFrame.interpolate(method="index") 来填充 NaN 值。
它有效并给了我预期的结果,但它花费了 3k 行数据帧高达 11 秒,我相信这与使用 apply() 方法结合创建一个新的用于为每一行插入数据的数据框。
是否有一种明显的方法可以“就地”获得相同的结果,例如,通过避免直接在原始数据帧上应用/新数据帧方法?
编辑: 使用 6 作为新长度参数调用函数时的预期输出为:
video_name
video_length
video_0
video_25
video_50
video_75
video_100
new_video_0
new_video_25
new_video_50
new_video_75
new_video_100
video_1
6
1000
500
300
250
5
1000
500
300
250
5
video_2
6
1000
500
300
250
5
1000
900
800
700
600
第一行将保持不变,因为视频已经有 6 秒长了。
在第二行中,视频将从 30 秒缩短为 6 秒,因此新的四分位数将位于 0、1.5、3、4.5、6 秒,统计数据将在 1000 和 500 之间插值,这是旧 0% 的值和 25% 的时间标记
EDIT2:我不在乎是否需要添加临时列,时间是个问题,内存不是。
作为参考,这是我的代码:
def get_value(marks, asset, mark_index) -> int:
value = marks["count"][asset["new_length_marks"][mark_index]]
if isinstance(value, pandas.Series):
res = value.iloc(0)
else:
res = value
return math.ceil(res)
def length_update_row(row, assets, **kwargs):
asset_name = row["asset_name"]
asset = assets[asset_name]
# assets is a dict containing the list of files and the old and "new" video marks
# pre-calculated
marks = pandas.DataFrame(data=[int(row["video_start"]), int(row["video_25"]), int(row["video_50"]), int(row["video_75"]), int(row["video_completed"])],
columns=["count"],
index=asset["old_length_marks"])
marks = marks.combine_first(pandas.DataFrame(data=NaN, columns=["count"], index=asset["new_length_marks"][1:]))
marks = marks.interpolate(method="index")
row["video_25"] = get_value(marks, asset, 1)
row["video_50"] = get_value(marks, asset, 2)
row["video_75"] = get_value(marks, asset, 3)
row["video_completed"] = get_value(marks, asset, 4)
return row
def length_update_stats(report: pandas.DataFrame,
assets: dict) -> pandas.DataFrame:
new_report = new_report.apply(lambda row: length_update_row(row, assets), axis=1)
return new_report
IIUC,你可以使用 np.interp:
# get the old x values
xs = df['video_length'].values[:, None] * [0, 0.25, 0.50, 0.75, 1]
# the corresponding y values
ys = df.iloc[:, 2:].values
# note that 6 is the new value
nxs = np.repeat(np.array(6), 2)[:, None] * [0, 0.25, 0.50, 0.75, 1]
res = pd.DataFrame(data=np.array([np.interp(nxi, xi, yi) for nxi, xi, yi in zip(nxs, xs, ys)]), columns="new_" + df.columns[2:] )
print(res)
输出
new_video_0 new_video_25 new_video_50 new_video_75 new_video_100
0 1000.0 500.0 300.0 250.0 5.0
1 1000.0 900.0 800.0 700.0 600.0
然后跨第二个轴连接:
output = pd.concat((df, res), axis=1)
print(output)
输出 (连接)
video_name video_length video_0 ... new_video_50 new_video_75 new_video_100
0 video_1 6 1000 ... 300.0 250.0 5.0
1 video_2 30 1000 ... 800.0 700.0 600.0
[2 rows x 12 columns]
我一直在努力解决 Pandas 的优化问题。
我开发了一个脚本来对相对较小的 DataFrame 的每一行应用计算(大约几千行,几十列)。 我严重依赖 apply() 函数,这在大多数情况下显然是一个糟糕的选择。
经过一轮优化,我只有一个方法需要时间,我还没有找到一个简单的解决方案:
基本上我的数据框包含一个视频观看统计列表,其中包含每个四分位数观看视频的人数(有多少人观看了 0%、25%、50% 等),例如:
video_name | video_length | video_0 | video_25 | video_50 | video_75 | video_100 |
---|---|---|---|---|---|---|
video_1 | 6 | 1000 | 500 | 300 | 250 | 5 |
video_2 | 30 | 1000 | 500 | 300 | 250 | 5 |
我正在尝试对统计数据进行插值,以便能够回答“如果视频持续 X 秒,有多少人会观看视频的每个四分位数”
现在我的函数接受数据帧和一个“new_length”参数,并在每一行调用 apply()。
处理每一行的函数计算每个四分位数的时间标记(因此 0、7.5、15、22.5 和 30 用于 30 秒的视频),以及给定新长度的每个四分位数的时间标记(以便减少30 秒视频到 6 秒,新的时间标记将是 0、1.5、3、4.5 和 6)。 我构建了一个数据框,其中包含时间标记作为索引,统计数据作为第一列中的值:
index (time marks) | view_stats |
---|---|
0 | 1000 |
7.5 | 500 |
15 | 300 |
22.5 | 250 |
30 | 5 |
1.5 | NaN |
3 | NaN |
4.5 | NaN |
然后我调用 DataFrame.interpolate(method="index") 来填充 NaN 值。
它有效并给了我预期的结果,但它花费了 3k 行数据帧高达 11 秒,我相信这与使用 apply() 方法结合创建一个新的用于为每一行插入数据的数据框。
是否有一种明显的方法可以“就地”获得相同的结果,例如,通过避免直接在原始数据帧上应用/新数据帧方法?
编辑: 使用 6 作为新长度参数调用函数时的预期输出为:
video_name | video_length | video_0 | video_25 | video_50 | video_75 | video_100 | new_video_0 | new_video_25 | new_video_50 | new_video_75 | new_video_100 |
---|---|---|---|---|---|---|---|---|---|---|---|
video_1 | 6 | 1000 | 500 | 300 | 250 | 5 | 1000 | 500 | 300 | 250 | 5 |
video_2 | 6 | 1000 | 500 | 300 | 250 | 5 | 1000 | 900 | 800 | 700 | 600 |
第一行将保持不变,因为视频已经有 6 秒长了。 在第二行中,视频将从 30 秒缩短为 6 秒,因此新的四分位数将位于 0、1.5、3、4.5、6 秒,统计数据将在 1000 和 500 之间插值,这是旧 0% 的值和 25% 的时间标记
EDIT2:我不在乎是否需要添加临时列,时间是个问题,内存不是。
作为参考,这是我的代码:
def get_value(marks, asset, mark_index) -> int:
value = marks["count"][asset["new_length_marks"][mark_index]]
if isinstance(value, pandas.Series):
res = value.iloc(0)
else:
res = value
return math.ceil(res)
def length_update_row(row, assets, **kwargs):
asset_name = row["asset_name"]
asset = assets[asset_name]
# assets is a dict containing the list of files and the old and "new" video marks
# pre-calculated
marks = pandas.DataFrame(data=[int(row["video_start"]), int(row["video_25"]), int(row["video_50"]), int(row["video_75"]), int(row["video_completed"])],
columns=["count"],
index=asset["old_length_marks"])
marks = marks.combine_first(pandas.DataFrame(data=NaN, columns=["count"], index=asset["new_length_marks"][1:]))
marks = marks.interpolate(method="index")
row["video_25"] = get_value(marks, asset, 1)
row["video_50"] = get_value(marks, asset, 2)
row["video_75"] = get_value(marks, asset, 3)
row["video_completed"] = get_value(marks, asset, 4)
return row
def length_update_stats(report: pandas.DataFrame,
assets: dict) -> pandas.DataFrame:
new_report = new_report.apply(lambda row: length_update_row(row, assets), axis=1)
return new_report
IIUC,你可以使用 np.interp:
# get the old x values
xs = df['video_length'].values[:, None] * [0, 0.25, 0.50, 0.75, 1]
# the corresponding y values
ys = df.iloc[:, 2:].values
# note that 6 is the new value
nxs = np.repeat(np.array(6), 2)[:, None] * [0, 0.25, 0.50, 0.75, 1]
res = pd.DataFrame(data=np.array([np.interp(nxi, xi, yi) for nxi, xi, yi in zip(nxs, xs, ys)]), columns="new_" + df.columns[2:] )
print(res)
输出
new_video_0 new_video_25 new_video_50 new_video_75 new_video_100
0 1000.0 500.0 300.0 250.0 5.0
1 1000.0 900.0 800.0 700.0 600.0
然后跨第二个轴连接:
output = pd.concat((df, res), axis=1)
print(output)
输出 (连接)
video_name video_length video_0 ... new_video_50 new_video_75 new_video_100
0 video_1 6 1000 ... 300.0 250.0 5.0
1 video_2 30 1000 ... 800.0 700.0 600.0
[2 rows x 12 columns]