如何解决作用于索引的向量化函数
TL;DR:我知道 .apply() 在熊猫中很慢。但是,我有一个作用于索引的函数,我无法弄清楚如何矢量化。我希望这个函数作用于两组参数(分别为 500,000 和 1,500 长)。我想生成一个数据框,其中第一个参数作为行索引,第二个参数作为列名,以及包含该特定行和列的函数输出的单元格。目前看来,代码需要几天才能运行。下面的更多详细信息和最小的可重现示例:
输入数据:
我有一系列独特的学生 ID,长度为 500,000 名学生。我有一个由这些学生 ID 索引的 df (exam_score_df),其中包含每个学生在数学、语言、历史和科学方面的相应分数。
我还有一系列学校代码(每个学校代码对应一所学校),一共1500所学校。我有一个由学校代码索引的 df (school_weight_df),包含学校在数学、语言、历史和科学方面的权重,用于计算学生的分数。每行还包含一个以“Y”或“N”为索引的“Alternative_score”,因为有些学校允许您选择历史和科学之间的最佳科目分数来计算您的总分。
我写的要矢量化的函数:
def calc_score(student_ID,program_code):
'''
For a given student and program,returns students score for that program.
'''
if school_weight_df.loc[program_code]['Alternative_score'] == 'N':
return np.dot(np.array(exam_score_df.loc[student_ID][['LANG','MAT','HIST','SCI']]),np.array(school_weight_df.loc[program_code][['%LANG','%MAT','%HIST','%sCI']]))
elif school_weight_df.loc[program_code]['Alternative_score'] == 'Y':
history_score = np.dot(np.array(exam_score_df.loc[student_ID][['LANG','HIST']]),'%HIST']]))
science_score = np.dot(np.array(exam_score_df.loc[student_ID][['LANG','%sCI']]))
return max(history_score,science_score)
示例 DF:
以下是exam_score_df 和school_weight_df 的dfs 示例:
student_data = [[3,620,688,638,688],[5,534,602,606,700],[9,487,611,477,578]]
exam_score_df = pd.DataFrame(student_data,columns = ['student_ID','LANG','SCI'])
exam_score_df.set_index('student_ID')
program_data = [[101,20,30,25,'N'],[102,40,10,50,'Y']]
school_weight_df = pd.DataFrame(program_data,columns = ['program_code','%LANG','%sCI','Alternative_score'])
school_weight_df.set_index('program_code',inplace = True)
这是用于索引以下代码的系列:
series_student_IDs = pd.Series(exam_score_df.index,inplace = True)
series_program_codes = pd.Series(school_weight_df.index,inplace = True)
为了创建每个程序中所有学生分数的 df,我使用了嵌套的 .apply():
new_df = pd.DataFrame(series_student_IDs.apply(lambda x: series_program_codes.apply(lambda y: calc_score(x,y))))
我已经阅读了几本关于在 Pandas 中优化代码的入门读物,包括写得很好的 Guide by Sofia Heisler。我的主要关注点,也是我无法弄清楚如何矢量化这段代码的原因,是我的函数需要对索引进行操作。我还有一个次要的问题是,即使我进行了矢量化,np.dot on large matrices 也存在这个问题,无论如何我都想对其进行循环。
感谢大家的帮助!我只写了几个月的代码,所以非常感谢所有有用的评论。
解决方法
申请 = 差,双重申请 = 非常差
如果您在函数中使用 Numpy,为什么不一路使用 Numpy?您仍然更喜欢批量方法,因为整个矩阵会占用大量内存。检查以下方法。
在低端 macbook pro 上,对一批 5000 名学生进行的每次迭代花费了我 2.05 秒。这意味着对于 500,000 名学生,您可以预期大约 200 秒,这还不错。
我对 100000 名学生和 1500 所学校进行了以下测试,总共花了我大约 30-40 秒。
-
首先,我创建了一个虚拟数据集:考试成绩(100,000 名学生,4 个分数)、学校权重(1500 所学校,4 个权重)和一个 布尔值 标志,学校有替代方案作为是或否,
Y==True
,N==False
-
接下来,对于一批 5000 名学生,我简单地使用
np.einsum
计算 2 个矩阵之间 4 个科目中每个科目的元素乘积。这给了我(5000,4)
*(1500,4)
->(1500,5000,4)
。将此视为点积的第一部分(没有和)。 -
我这样做的原因是因为这对于您的条件 N 或 Y 来说都是必要的步骤。
-
接下来,
的学校FOR N:
我只是根据alt_flag
过滤上面的矩阵,在最后一个轴上减少它(总和)并转置得到(5000,766)
,其中 766 是数字alternative == N
-
FOR Y:
,我根据alt_flag
进行过滤,然后计算前 2 个主题的总和(因为它们很常见)并将它们分别添加到第 3 个和第 4 个主题中,取一个最大值并将其作为我的最终分数返回。发布一个转置。这给了我(5000,734)
。 -
我对每批 5000 人执行此操作,直到我附加了所有批次,然后只需
np.vstack
即可获得最终表(100000,766)
和(100000,734)
。 -
现在我可以简单地将这些叠加到 axis=0 上以获得
(100000,1500)
但如果我想将它们映射到 ID(学生、学校),使用 {{ 1}} 然后将它们组合起来。为您阅读最后一步。 -
最后一步是由您执行,因为我没有完整的数据集。由于索引的顺序是通过批量向量化保留的,您现在可以简单地将 766 个学校 ID 与 N、734 个学校 ID 与 Y 和 100000 个学生 ID 映射,按照它们在主数据集中出现的顺序。然后简单地附加 2 个数据帧以创建最终的(海量)数据帧。
-
注意:您必须在 for 循环中将 100000 更改为 500000,不要忘记!!
pd.DataFrame(data,columns=list_of_schools_alt_Y,index=list_of_student_ids
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。