【问题标题】:Speeding up python when using nested for and if loops使用嵌套的 for 和 if 循环时加速 python
【发布时间】:2022-01-14 03:09:12
【问题描述】:

我有一个 csv 文件,其中有一个名为“作者”的列。在该列中,每一行都有几个作者,用逗号分隔。在函数下方的代码中,getAuthorNames 获取该列中的所有作者姓名,并返回一个包含所有作者姓名的数组。

然后函数 authCount 计算个人姓名在“作者”列中出现的次数。起初,我用几百行来做,没有任何问题。现在我正在尝试使用 20,000 行以上来完成它,它已经花费了几个小时,但仍然没有结果。我相信是嵌套的 for 循环和 if 语句导致它花费了这么长时间。任何关于如何加快进程的建议都会有所帮助。我应该使用 lambda 吗?是否有内置的 pandas 功能可以提供帮助?

这是输入数据的样子:

Title,Authors,ID
XXX,"Wang J, Wang H",XXX
XXX,"Wang J,Han H",XXX

这就是输出的样子

Author,Count
Wang J,2
Wang H,1
Han H,1

代码如下:

    import pandas as pd


    df = pd.read_csv (r'C:\Users\amos.epelman\Desktop\Pubmedpull3GC.csv')


    def getAuthorNames(dataFrame):
        arrayOfAuthors = []
        numRows = dataFrame.shape[0]

        cleanDF = dataFrame.fillna("0")

        for i in range (0,numRows):
            miniArray = cleanDF.at[i,"Authors"].split(",")
            arrayOfAuthors += miniArray
    
        return arrayOfAuthors


    def authCount(dataFrame):
        authArray = getAuthorNames(dataFrame)
        numAuthors = len(authArray)
        countOfAuth = [0] * numAuthors

        newDF = pd.DataFrame({"Author Name": authArray, "Count": countOfAuth})
        refDF = dataFrame.fillna("0")


        numRows= refDF.shape[0]


        for i in range (0,numAuthors):
            for j in range (0,numRows):
                if newDF.at[i, "Author Name"] in refDF.at[j,"Authors"]:
                    newDF.at[i,"Count"] += 1
            
        sortedDF = newDF.sort_values(["Count"], ascending = False)

        noDupsDF = sortedDF.drop_duplicates(subset ="Author Name", keep = False)

        return noDupsDF




    finalDF = authCount(df)
    file_name = 'GC Pubmed Pull3 Author Names with Count.xlsx'
    finalDF.to_excel(file_name)

【问题讨论】:

  • 您正在做的是一种非常非常缓慢的方式来处理 pandas 数据框。您可以尝试使用 lambda 函数之类的东西对行进行操作。类似newDF["Count"] = newDF.apply(lambda row: some function of row, axis=1)(而不是你的 for 循环)
  • 您应该提供数据样本(作为文本)和预期输出

标签: python pandas csv nested


【解决方案1】:

您可以使用 Python 标准库中的 csv readercollections Counter 类来做到这一点。

我制作了一个包含 20K 行随机生成的名称的示例 CSV,random_names.csv

Authors
"Darnel D, Blythe B"
"Wang H, Darnel D, Alice A"
"Wang J, Wang H, Darnel D, Blythe B"
"Han H, Wang J"
"Clarice C, Wang H, Darnel D, Alice A"
"Clarice C, Han H, Blythe B, Wang J"
"Clarice C, Darnel D, Blythe B"
"Clarice C, Wang H, Blythe B"
"Blythe B, Wang J, Darnel D"
...

我的代码没有排序,但指出了插入排序的位置。不到一秒(在我的 M1 Macbook Air 上):

import csv
from collections import Counter

author_counts = Counter()

with open('random_names.csv', newline='') as f:
    reader = csv.reader(f)
    next(reader)  # discard header

    for row in reader:
        authors = row[0]  # !! adjust for your data
        for author in authors.split(','):
            author_counts.update([author.strip()])

# Sort here
print(author_counts.items())

with open('output.csv', 'w', newline='') as f:
    writer = csv.writer(f)
    writer.writerow(['Author','Count'])
    writer.writerows(author_counts.items())

它打印出了这个调试行:

dict_items([('Darnel D', 10690), ('Blythe B', 10645), ('Wang H', 10881), ('Alice A', 10750), ('Wang J', 10613), ('Han H', 10814), ('Clarice C', 10724)])

并将其保存为 output.csv

Author,Count
Darnel D,10690
Blythe B,10645
Wang H,10881
Alice A,10750
Wang J,10613
Han H,10814
Clarice C,10724

【讨论】:

    【解决方案2】:

    您可以尝试使用 Counter 和 lambda 函数来消除两个数据帧上的嵌套 for 循环,这似乎是添加新列的缓慢方法

    from collections import Counter
    

    然后得到“计数”列

    author_counts = Counter(list(refDF["Authors"]))
    
    newDF["Count"] = newDF.apply(lambda r: author_counts[r["Author Name"]], axis=1)
    

    【讨论】:

    • 谢谢。我认为这并不完全有效,因为原始文件中的作者姓名是由逗号分隔的作者列表。当我看到 author_counts 的结果时,它会告诉我一篇论文的作者列表,而不是单个作者。我认为这是正确的轨道
    • 好的,那么您可能最终不会使用 Counter。但绝对不要遍历两个数据框的所有行来创建一个新列。作为第一步,您可能想要查看轴 1 上的 lambda 函数。
    【解决方案3】:
    # take series of authors and split at comma and expand into dataframe
    authors = df['author'].str.split(pat=',', expand=True)
    authors.melt().value_counts()
    

    我不确定它是否更快,但这应该会为您提供唯一值 计数。

    输入:

    x y z author book
    0 0 0 aa,bb,cc l
    0 0 0 a,b,c l
    0 0 0 aa,bb,c l
    0 0 0 aa,b,c l
    

    输出:

    variable  value
    0         aa       3
    2         c        3
    1         b        2
              bb       2
    0         a        1
    2         cc       1
    dtype: int64
    

    更新:
    此解决方案对输出进行排序而不保存到文件,%%timeit% 给出:
    7.03 ms ± 396 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

    @ZachYoung 解决方案不排序且不保存输出 %%timeit 给出:
    5.64 ms ± 208 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

    我在一个有 8000 个名字的测试文件上运行了这个

    【讨论】:

    • 据我了解,pandas 的独特功能不适用于数据框,因此您必须选择特定的列。在这种情况下,当我使用您的方法时,作者会被分成多个列,但感谢您的帮助!
    • 也许更新后的答案对你有用
    • 我忘了你想要一个计数,而不是唯一作者的列表。
    猜你喜欢
    • 1970-01-01
    • 2021-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多