处理超过 700K 行数据的 csv 文件

数据挖掘 Python 熊猫 数据框 CSV
2022-02-17 21:22:28

我有一个 .csv 文件(大小约为 400MB),其中包含 700K 行结构化数据。表结构为:


+----+----------+-------+-----------+--------+--------+
| Id | Category | List1 |   List2   | Value1 | Value2 |
+----+----------+-------+-----------+--------+--------+
|  1 |        1 | A,B,C | Cat1,Cat2 |    100 |      5 |
|  2 |        1 | D,F   | Cat1,Cat4 |    120 |      4 |
|  3 |        2 | E,A   | Cat3      |    140 |      2 |
|  4 |        2 | E,A   | NULL      |    110 |      3 |
|  5 |        3 | B     | Cat2      |    100 |      6 |
+----+----------+-------+-----------+--------+--------+

对于我要计算的表的每一行 (CurrentRow):
CurrentRow.Value3 = SUM(Value1)/SUM(Value2) 表中满足以下条件的所有其他行 (OtherRow):

Condition1 = CurrentRow.Category != OtherRow.Category
Condition2 = CurrentRow.List1 intersects OtherRow.List1  
Condition3 = CurrentRow.List2 intersects OtherRow.List2 or CurrentRow.List2 = NULL or OtherRow.List2 = NULL

另外,我想列出计算 Value3 所涉及的行的 ID。

第一行的示例:
Condition1:第一行的类别值为 1。因此,第 3-4 行满足条件 #1,因为它们的类别值不等于 1。
Condition2:列“List1”的值为“A,B,C " 与值“E,A”(第 3 行)、“E,A”(第 4 行)、“B”(第 5 行)相交。
条件3:列“List2”的值“Cat1,Cat2”与值“Cat1,Cat4”(第 2 行)、“Cat2”(第 5 行)相交,并且我们采用第 4 行,因为它具有“NULL”值.

因此,我们选择第 4 行和第 5 行,因为它们都满足所有条件。
值 3 = (110+100)/(3+6) = 210/9 = 23.33
Ids = "4,5"

上表的结果将是:

+----+----------+-------+-----------+--------+--------+--------+------+
| Id | Category | List1 |   List2   | Value1 | Value2 | Value3 | Ids  |
+----+----------+-------+-----------+--------+--------+--------+------+
|  1 |        1 | A,B,C | Cat1,Cat2 |    100 |      5 | 23.33  | 4,5  |
|  2 |        1 | D,F   | Cat1,Cat4 |    120 |      4 | NULL   | NULL |
|  3 |        2 | E,A   | Cat3      |    140 |      2 | NULL   | NULL |
|  4 |        2 | E,A   | NULL      |    110 |      3 | 20     | 1    |
|  5 |        3 | B     | Cat2      |    100 |      6 | 20     | 1    |
+----+----------+-------+-----------+--------+--------+--------+------+

我曾尝试在 python 中使用 pandas 来做到这一点:

data = pd.read_pickle('data.df')
for _index, _record in data.iterrows():
    category = _record["Category"]
    list1 = _record["List1"]
    list2 = _record["list2"]
    candidates = data.loc[(data['Category'] != category) & (pd.Series(list1).isin(data["List1"]).any()) & ((pd.Series(list2).isin(data["List2"]).any()) | (data["List2"][0] == 'NULL') | (list2[0] == 'NULL'))]
    value1_sum = candidates["Value1"].sum()
    value2_sum = candidates["Value2"].sum()
    ids = candidates[['Id']].to_numpy()
    if value2_sum > 0:
        data.loc[_index, "Value3"] = value1_sum/ value2_sum
        data.loc[_index, "Ids"] = ids
data.to_csv('result.csv')

它适用于少量行,但需要永远处理 700K 行。有什么方法可以优化这个算法吗?

1个回答

我认为主要的耗时将是您要逐一迭代超过 700,000 行。

对于所有行,您也许可以先进行一些检查/比较(与按位的东西一起使用:&|) 。因此,对于该行,它现在看起来像这样:

data.loc[
    (data['Category'] != category) & 
    (pd.Series(list1).isin(data["List1"]).any()) & 
    (pd.Series(list2).isin(data["List2"]).any()) |
    (data["List2"][0] == 'NULL') |
    (list2[0] == 'NULL'))]

在循环之前,您可以预先计算每一行,将每次比较的结果添加为新列:

data["check1"] = data['Category'] != category
data["check2"] = ...
data["check3"] = ...
data["check4"] = ...
data["check5"] = data.List2 == "NULL"

现在在您的行中使用这些列的值进行所有比较。


为了更好地了解它一直占用的内容,您可以分析代码,例如使用CProfile模块。为此,您可以只运行数据的前 100 行 - 分析工具将提供汇总统计信息。


如果您真的想深入了解它,请查看Pandas 性能改进方法,其中包含使用 Numba 和 Cython。这里可能会有巨大的收益,但你可能需要投入更多的努力。Numba是最容易开始的;除非你有 C 语言的经验。