【问题标题】:improve upon mapped lambdas in Python (pandas)改进 Python (pandas) 中的映射 lambda
【发布时间】:2015-07-26 13:31:13
【问题描述】:

我正在消化几个 csv 文件(每个文件都有一年或一年以上的数据),以将医疗分类为广泛的类别,同时仅保留原始信息的子集,甚至汇总到每月的数字(按 AR=年和月)每人的治疗次数(LopNr)。许多治疗同时属于不同的类别(多个诊断代码列在 csv 的相关列中,因此我将该字段分成一列列表,并按属于 ICD-9 相关范围的任何诊断代码对行进行分类代码)。

我正在使用 IOPro 来节省内存,但我仍然遇到了段错误(仍在调查中)。每个文本文件有几个 GB,但是这台机器有 256 GB RAM。要么其中一个包有问题,要么我需要一个内存效率更高的解决方案。

我在 Linux 下使用版本 pandas 0.16.2 np19py26_0、iopro 1.7.1 np19py27_p0 和 python 2.7.10 0。

所以原始数据看起来像这样:

LopNr   AR INDATUMA DIAGNOS …
1     2007 20070812 C32 F17
1     2007 20070816     C36

我希望看到这样的聚合:

LopNr   AR month tobacco …
1     2007     8       2

顺便说一句,我最终需要 Stata dta 文件,但我通过 cvs 因为 pandas.DataFrame.to_stata 在我的经验中似乎很不稳定,但也许我也遗漏了一些东西。

# -*- coding: utf-8 -*-
import iopro
import numpy as np
from pandas import *

all_treatments  = DataFrame()
filelist = ['oppenvard20012005','oppenvard20062010','oppenvard2011','oppenvard2012','slutenvard1997','slutenvard2011','slutenvard2012','slutenvard19982004','slutenvard20052010']

tobacco = lambda lst: any( (((x >= 'C30') and (x<'C40')) or ((x >= 'F17') and (x<'F18')))  for x in lst)
nutrition = lambda lst: any( (((x >= 'D50') and (x<'D54')) or ((x >= 'E10') and (x<'E15')) or ((x >= 'E40') and (x<'E47')) or ((x >= 'E50') and (x<'E69')))  for x in lst)
mental = lambda lst: any( (((x >= 'F') and (x<'G')) )  for x in lst)
alcohol = lambda lst: any( (((x >= 'F10') and (x<'F11')) or ((x >= 'K70') and (x<'K71')))  for x in lst)
circulatory = lambda lst: any( (((x >= 'I') and (x<'J')) )  for x in lst)
dental = lambda lst: any( (((x >= 'K02') and (x<'K04')) )  for x in lst)
accident = lambda lst: any( (((x >= 'V01') and (x<'X60')) )  for x in lst)
selfharm = lambda lst: any( (((x >= 'X60') and (x<'X85')) )  for x in lst)
cancer = lambda lst: any( (((x >= 'C') and (x<'D')) )  for x in lst)
endonutrimetab = lambda lst: any( (((x >= 'E') and (x<'F')) )  for x in lst)
pregnancy = lambda lst: any( (((x >= 'O') and (x<'P')) )  for x in lst)
other_stress = lambda lst: any( (((x >= 'J00') and (x<'J48')) or ((x >= 'L20') and (x<'L66')) or ((x >= 'K20') and (x<'K60')) or ((x >= 'R') and (x<'S')) or ((x >= 'X86') and (x<'Z77')))  for x in lst)

for file in filelist:
    filename = 'PATH' + file +'.txt'
    adapter = iopro.text_adapter(filename,parser='csv',field_names=True,output='dataframe',delimiter='\t')
    treatments = adapter[['LopNr','AR','DIAGNOS','INDATUMA']][:]
    treatments['month'] = treatments['INDATUMA'] % 10000
    treatments['day'] = treatments['INDATUMA'] % 100
    treatments['month'] = (treatments['month']-treatments['day'])/100  
    del treatments['day']
    diagnoses = treatments['DIAGNOS'].str.split(' ')
    del treatments['DIAGNOS']
    treatments['tobacco'] = diagnoses.map(tobacco)
    treatments['nutrition'] = diagnoses.map(nutrition)
    treatments['mental'] = diagnoses.map(mental)
    treatments['alcohol'] = diagnoses.map(alcohol)
    treatments['circulatory'] = diagnoses.map(circulatory)
    treatments['dental'] = diagnoses.map(dental)
    treatments['accident'] = diagnoses.map(accident)
    treatments['selfharm'] = diagnoses.map(selfharm)
    treatments['cancer'] = diagnoses.map(cancer)
    treatments['endonutrimetab'] = diagnoses.map(endonutrimetab)
    treatments['pregnancy'] = diagnoses.map(pregnancy)
    treatments['other_stress'] = diagnoses.map(other_stress)
    all_treatments = all_treatments.append(treatments)
all_treatments = all_treatments.groupby(['LopNr','AR','month']).aggregate(np.count_nonzero) #.sum()
all_treatments = all_treatments.astype(int,copy=False,raise_on_error=False)
all_treatments.to_csv('PATH.csv')

【问题讨论】:

  • 您正在对函数进行大量范围检查。您可以将(x &gt;= 'C30') and (x &lt; 'C40') 之类的内容简化为('C30' &lt;= x &lt; 'C40')
  • 另外,像((x &gt;= 'O') and (x &lt; 'P')) 这样的东西可以简化为x.startswith('O')
  • 请注意,我可以通过避免使用 IOPro 来避免段错误。尽管如此,答案的所有其他改进都极大地改进了代码。

标签: python csv pandas lambda


【解决方案1】:

我认为您需要找到一种方法来矢量化您的解决方案。使用 map 和 lambda 函数效率不高,也没有利用使 pandas 如此有用的加速。很难确定,因为您还没有发布示例数据,但我认为一个很好的起点是做

diagnoses = treatments['DIAGNOS'].str.split(expand=True)

结果将是一个数据框,其中每个单词(或拆分结果中的元素)都有一列。然后,您可以对整个 DataFrame 进行矢量化比较。它可能看起来像这样:

between_c_vals = (diagnoses >= 'C30') & (diagnoses <= 'C40')
between_f_vals = (diagnoses >= 'F17') & (diagnoses <= 'F18')
treatment['tobacco'] = (between_c_vals | between_f_vals).any(axis=1)

这应该比在 Python 中使用循环的 .map 快​​数百倍。请注意,位运算符&amp;| 可用于对布尔向量和矩阵(或数据帧)执行集合逻辑。 如果您展示了treatment['DIAGNOS'] 的示例,我可能会提供更多帮助。需要注意的一件事是 NaN 在进行比较时的值,因为将 NaN 与任何东西进行比较总是返回 False 但我认为这里应该没问题,因为它不会返回任何不需要的 True 值。

【讨论】:

  • 如果大多数新列将丢失 (NaN),拆分列是否有效?我认为这意味着与最长的 DIAGNOS 列表一样多的新列,而大多数其他行只列出了几个 DIAGNOS。新列不会内存不足吗?否则我很欣赏矢量化的原理。
  • 嗯,它可能会,我不确定在这种情况下如何处理内存(虽然你有 256 GB?)。也许熊猫会聪明地处理它。正如我所说,我很难说,因为你没有发布示例数据,所以我不知道它的结构。尽管如此,我认为这是一种更可取的方法。如果内存有问题,您可以分块进行。我认为它仍然会快数百倍。
【解决方案2】:

几个cmets:

  1. 如上所述,您应该简化 lambda 表达式以提高可读性,可能使用 def

例子:

def tobacco(codes):
    return any( 'C30' <= x < 'C40' or
                'F17' <= x < 'F18'  for x in codes)

您还可以将这些函数矢量化,如下所示:

def tobacco(codes_column):
    return [any('C30' <= code < 'C40' or
                'F17' <= code < 'F18'
                for code in codes) if codes else False
            for codes in codes_column]

diagnoses = all_treatments['DIAGNOS'].str.split(' ').tolist()
all_treatments['tobacco'] = tobacco(diagnoses)
  1. 你将all_treatments初始化为一个DataFrame,然后追加到它上面。这是非常低效的。试试all_treatments = list(),然后在groupby 之前的循环外添加all_treatments = pd.concat(all_treatments, ignore_index=True)。另外,应该是all_treatments.append(treatments)(对比all_treatments = all_treatments.append(treatments)

  2. 为了分组目的计算月份,你可以使用:

    all_treatments['month'] = all_treatments.INDATUMA % 10000 // 100

  3. 最后,不要在读取每个文件后将 lambda 函数应用于每个文件,而是尝试将它们应用于 all_treatments DataFrame。

附言您可能还想在您的 groupby 语句中尝试 .sum() 而不是 .aggregate(np.count_nonzero)

【讨论】:

  • 这很棒。你会用 numba 以不同的方式构建矢量化函数吗?也许不值得为列表数组的字符串函数付出努力?
  • 我没有用过 numba 的字符串,所以我不确定它会如何执行。
  • 所以如果我在 def 之前简单地@numba.jit,不会有太大变化吗?可能是。这是链接:pandas.pydata.org/pandas-docs/stable/…
  • 我正在尝试这一切,但到目前为止我正在修复这个错误:all_treatments = all_treatments.append(treatments)AttributeError: 'NoneType' object has no attribute 'append'
  • 第一次尝试追加时变为无。之前它是一个空列表,正如启动的那样。
猜你喜欢
  • 2021-06-11
  • 2016-12-16
  • 1970-01-01
  • 2018-08-29
  • 2022-01-19
  • 2016-01-27
  • 2021-06-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多