【问题标题】:Fastest way to iterate function over pandas dataframe在 pandas 数据帧上迭代函数的最快方法
【发布时间】:2020-07-30 21:33:37
【问题描述】:

我有一个函数可以对 csv 文件的行进行操作,根据是否满足条件将不同单元格的值添加到字典中:

df = pd.concat([pd.read_csv(filename) for filename in args.csv], ignore_index = True)

ID_Use_Totals = {}
ID_Order_Dates = {}
ID_Received_Dates = {}
ID_Refs = {}
IDs = args.ID

def TSQs(row):

    global ID_Use_Totals, ID_Order_Dates, ID_Received_Dates

    if row['Stock Item'] not in IDs:
        pass
    else:
        if row['Action'] in ['Order/Resupply', 'Cons. Purchase']:
            if row['Stock Item'] not in ID_Order_Dates:
                ID_Order_Dates[row['Stock Item']] = [{row['Ref']: pd.to_datetime(row['TransDate'])}]
            else:
                ID_Order_Dates[row['Stock Item']].append({row['Ref']: pd.to_datetime(row['TransDate'])})
        
        elif row['Action'] == 'Received':
                
             if row['Stock Item'] not in ID_Received_Dates:
                ID_Received_Dates[row['Stock Item']] = [{row['Ref']: pd.to_datetime(row['TransDate'])}]
            else:
                ID_Received_Dates[row['Stock Item']].append({row['Ref']: pd.to_datetime(row['TransDate'])})
                                    
        elif row['Action'] == 'Use':
            if row['Stock Item'] in ID_Use_Totals: 
                ID_Use_Totals[row['Stock Item']].append(row['Qty'])
            else:
                ID_Use_Totals[row['Stock Item']] = [row['Qty']]
                                       
        else:
            pass

目前,我正在做:

for index, row in df.iterrows():
    TSQs(row)

timer() 为 40,000 行的 csv 文件返回 70 到 90 秒。

我想知道在整个数据帧(可能是数十万行)上实现这一点的最快方法是什么。

【问题讨论】:

    标签: python python-3.x pandas numpy


    【解决方案1】:

    我敢打赌,不使用 Pandas 会更快。

    此外,您可以使用defaultdicts 来避免检查您是否看过给定的产品:

    import csv
    import collections
    import datetime
    
    ID_Use_Totals = collections.defaultdict(list)
    ID_Order_Dates = collections.defaultdict(list)
    ID_Received_Dates = collections.defaultdict(list)
    ID_Refs = {}
    IDs = set(args.ID)
    order_actions = {"Order/Resupply", "Cons. Purchase"}
    
    for filename in args.csv:
        with open(filename) as f:
            for row in csv.DictReader(f):
                item = row["Stock Item"]
                if item not in IDs:
                    continue
                ref = row["Ref"]
                action = row["Action"]
                if action in order_actions:
                    date = datetime.datetime.fromisoformat(row["TransDate"])
                    ID_Order_Dates[item].append({ref: date})
                elif action == "Received":
                    date = datetime.datetime.fromisoformat(row["TransDate"])
                    ID_Received_Dates[item].append({ref: date})
                elif action == "Use":
                    ID_Use_Totals[item].append(row["Qty"])
    

    编辑:如果 CSV 真的是这种形式

    "Employee", "Stock Location", "Stock Item"
    "Ordered", "16", "32142"
    

    stock CSV 模块无法完全解析它。

    您可以使用 Pandas 解析文件,然后遍历行,但我不确定这最终是否会更快:

    import collections
    import datetime
    import pandas
    
    ID_Use_Totals = collections.defaultdict(list)
    ID_Order_Dates = collections.defaultdict(list)
    ID_Received_Dates = collections.defaultdict(list)
    ID_Refs = {}
    IDs = set(args.ID)
    order_actions = {"Order/Resupply", "Cons. Purchase"}
    
    for filename in args.csv:
        for index, row in pd.read_csv(filename).iterrows():
            item = row["Stock Item"]
            if item not in IDs:
                continue
            ref = row["Ref"]
            action = row["Action"]
            if action in order_actions:
                date = datetime.datetime.fromisoformat(row["TransDate"])
                ID_Order_Dates[item].append({ref: date})
            elif action == "Received":
                date = datetime.datetime.fromisoformat(row["TransDate"])
                ID_Received_Dates[item].append({ref: date})
            elif action == "Use":
                ID_Use_Totals[item].append(row["Qty"])
    
    

    【讨论】:

    • 我在row["Stock Item"] 上得到一个KeyError,打印第一行给出:OrderedDict([(None, ['Employee', 'Stock Location', 'Stock Item', 'Supplier', 'TransDate', 'When should the stock team receive this', 'Applied', 'Qty', 'Adjustment', 'UnitPrice', 'Total', 'State', 'Action', 'Ref'])])
    • 我认为您的 CSV 文件可能具有与 csv 模块默认值不同的分隔符,并且它解析错误...
    • 你知道定义分隔符的方法吗?
    • 是的——您需要将合适的 CSV 方言对象传递给 csv.DictReader() 构造函数(参见 docs.python.org/3/library/csv.html#csv.Dialect)。 CSV 是什么样的,实际的分隔符是什么?
    • 在文本文件中打开它显示它的格式为"Ordered", "16", "32142" 例如
    【解决方案2】:

    您可以使用应用功能。代码将如下所示:

    df.apply(TSQs, axis=1)
    

    这里当axis=1 时,每一行都将作为pd.Series 发送到函数TSQs,您可以从那里像row["Ref"] 一样索引以获取该行的值。由于这是一个向量操作,所以在 for 循环之后会运行很多次。

    【讨论】:

      【解决方案3】:

      完全不迭代可能最快:

      # Build some boolean indices for your various conditions
      idx_stock_item = df["Stock Item"].isin(IDs)
      idx_purchases =  df["Action"].isin(['Order/Resupply', 'Cons. Purchase'])
      idx_order_dates = df["Stock Item"].isin(ID_Order_Dates)
      
      # combine the indices to act on specific rows all at once
      idx_combined = idx_stock_item & idx_purchases & ~idx_order_dates
      # It looks like you were putting a single entry dictionary in each row - wouldn't it make sense to rather just use two columns? i.e. take advantage of the DataFrame data structure
      ID_Order_Dates.loc[df.loc[idx_combined, "Stock Item"], "Ref"] = df.loc[idx_combined, "Ref"]   
      ID_Order_Dates.loc[df.loc[idx_combined, "Stock Item"], "Date"] = df.loc[idx_combined, "TransDate"]
      
      # repeat for your other cases
      # ...
      

      【讨论】:

      • 我主要是用字典来方便之后的处理。我正在获取订单/接收日期来计算不同库存项目的平均交货时间,然后将其与使用率一起使用来获取目标库存数量 - 我不确定通过向数据框中添加行来获得什么,但我对熊猫很陌生
      • 这不是添加行,它只是使用两列。即您的 ref 在一列中,而您的日期在同一行的另一列中。这样,您仍然可以轻松获取给定行的两条信息,但在 pandas 中工作会容易得多。
      猜你喜欢
      • 1970-01-01
      • 2022-01-18
      • 2020-10-05
      • 1970-01-01
      • 1970-01-01
      • 2013-10-23
      • 2019-10-09
      • 1970-01-01
      • 2020-05-09
      相关资源
      最近更新 更多