达斯平展字典专栏

时间:2019-05-13 19:20:02

标签: python pandas dask flatten

我是Dask的新手,正在寻找一种方法来展平PANDAS数据框中的字典列。这是一个1600万行数据帧的第一行的屏幕截图:

screenshot of first two rows of data

这是来自三行的文本示例:

{{u'F9_07_PZ_COMP_DIRECT': u'0', u'F9_07_PZ_DIRTRSTKEY_NAME': u'DEBRA MEALY', u'F9_07_PZ_COMP_OTHER': u'0', u'F9_07_PZ_COMP_RELATED': u'0', u'F9_07_PZ_TITLE': u'CHAIR PERSON', u'F9_07_PZ_AVE_HOURS_WEEK': u'1.00', u'F9_07_PC_TRUSTEE_INDIVIDUAL': u'X'}, {u'F9_07_PZ_COMP_DIRECT': u'0', u'F9_07_PZ_DIRTRSTKEY_NAME': u'HELEN GORDON', u'F9_07_PZ_COMP_OTHER': u'0', u'F9_07_PZ_COMP_RELATED': u'0', u'F9_07_PZ_TITLE': u'VICE CHAIR', u'F9_07_PZ_AVE_HOURS_WEEK': u'1.00', u'F9_07_PC_TRUSTEE_INDIVIDUAL': u'X'}, {'F9_07_PC_HIGH_COMP_EMPLOYEE': 'X', 'F9_07_PZ_DIRTRSTKEY_NAME': 'ROB S KHANUJA', 'F9_07_PZ_COMP_OTHER': '14902', 'F9_07_PZ_COMP_RELATED': '0', 'F9_07_PZ_TITLE': 'EXEC. DIR. OPERATIONS', 'F9_07_PZ_AVE_HOURS_WEEK': '40.00', 'F9_07_PZ_COMP_DIRECT': '133173'}}

我通常会使用以下代码来平整 Form990PartVIISectionAGrp 列:

    df = pd.concat([df.drop(['Form990PartVIISectionAGrp'], axis=1), df['Form990PartVIISectionAGrp'].swifter.apply(pd.Series)], axis=1)

我正在Dask中进行此操作,但出现以下错误:“ ValueError:计算数据中的列与提供的元数据中的列不匹配。”

我正在使用Python 2.7。我导入了相关的软件包

    from dask import dataframe as dd
    from dask.multiprocessing import get
    from multiprocessing import cpu_count
    nCores = cpu_count()

为了测试代码,我创建了数据的随机样本:

    dfs = df.sample(1000)

然后生成Dask数据框:

    ddf = dd.from_pandas(dfs, npartitions=nCores)

该列当前为字符串格式,因此我将其转换为字典。通常,我只写一行代码:

dfs['Form990PartVIISectionAGrp'] = dfs['Form990PartVIISectionAGrp'].apply(literal_eval) 

但是我在这里尝试以一种更类似于“ Dask”的形式进行操作,因此我编写了以下函数,然后将其应用:

    def make_dict(dfs):
        dfs['Form990PartVIISectionAGrp'] = dfs['Form990PartVIISectionAGrp'].apply(literal_eval)   
        return dfs
    ddf_out = ddf.map_partitions(make_dict, meta=dfs[:0]).compute()

这有效-它返回一个PANDAS数据帧,其中Form990PartVIISectionAGrp列采用字典格式(但是,它的速度不比非Dask应用的速度快)。

ddf_out

然后我重新创建Dask DF:

    ddf = dd.from_pandas(ddf_out, npartitions=nCores)

并编写一个使列变平的函数:

    def flatten(ddf_out):
        ddf_out = pd.concat([ddf_out.drop(['Form990PartVIISectionAGrp'], axis=1), ddf_out['Form990PartVIISectionAGrp'].apply(pd.Series)], axis=1)
        #ddf_out = ddf_out['Form990PartVIISectionAGrp'].apply(pd.Series)
    return ddf_out

如果我随后运行此代码:

    result = ddf.map_partitions(flatten)

我得到以下输出,其中列尚未展平:

result

我也遇到有关丢失元数据的错误,并且鉴于上述内容无助于解析字典列,因此我创建了一个列表,这些列是由普通的Python平展列产生的,并用于创建字典列和数据类型:

metadir = {u'BusinessName': 'O', u'F9_07_PC_FORMER': 'O', u'F9_07_PC_HIGH_COMP_EMPLOYEE': 'O',
       u'F9_07_PC_KEY_EMPLOYEE': 'O', u'F9_07_PC_OFFICER': 'O',
       u'F9_07_PC_TRUSTEE_INDIVIDUAL': 'O', u'F9_07_PC_TRUSTEE_INSTITUTIONAL': 'O',
       u'F9_07_PZ_AVE_HOURS_WEEK': 'O', u'F9_07_PZ_AVE_HOURS_WEEK_RELATED': 'O',
       u'F9_07_PZ_COMP_DIRECT': 'O', u'F9_07_PZ_COMP_OTHER': 'O',
       u'F9_07_PZ_COMP_RELATED': 'O', u'F9_07_PZ_DIRTRSTKEY_NAME': 'O',
       u'F9_07_PZ_TITLE': 'O', u'NameBusiness': 'O', u'URL': 'O'}

然后我将flatten函数与此元数据一起应用:

    result = ddf.map_partitions(flatten, meta=metadir)

我得到以下输出结果:

result

运行result.columns会产生以下结果:

result.columns

失败的地方是运行compute(),我收到以下错误消息:“ ValueError:计算数据中的列与提供的元数据中的列不匹配。”我是否写同样的错误:

result.compute()

result.compute(meta=metadir)

我不确定我在做什么错。结果 中的列似乎与 metadir 中的列匹配。任何建议将不胜感激。

更新: 这是我更新展平功能的过程。

    meta = pd.DataFrame(columns=['URL', 'F9_07_PC_TRUSTEE_INDIVIDUAL',
     'F9_07_PZ_DIRTRSTKEY_NAME',
     'F9_07_PZ_COMP_OTHER',
     'F9_07_PZ_COMP_RELATED',
     'F9_07_PZ_TITLE',
     'F9_07_PZ_AVE_HOURS_WEEK',
     'F9_07_PZ_COMP_DIRECT',
     'F9_07_PZ_AVE_HOURS_WEEK_RELATED',
     'F9_07_PC_OFFICER',
     'F9_07_PC_HIGH_COMP_EMPLOYEE',
     'BusinessName',
     'F9_07_PC_KEY_EMPLOYEE',
     'F9_07_PC_TRUSTEE_INSTITUTIONAL',
     'NameBusiness',
     'F9_07_PC_FORMER'], dtype="O")

    def flatten(ddf_out):
        ddf_out = pd.concat([df.drop(['Form990PartVIISectionAGrp'], axis=1), df['Form990PartVIISectionAGrp'].apply(pd.Series)], axis=1)
        for m in meta:
            if m not in ddf_out:
                df[m] = '' 
        return ddf_out

然后我跑:

result = ddf.map_partitions(flatten, meta=meta).compute()

2 个答案:

答案 0 :(得分:2)

一些注意事项:

  

.apply(literal_eval)

map一样好吗?

  

然后我重新创建Dask DF:

     

ddf = dd.from_pandas(ddf_out,npartitions = nCores)

ddf_out已经是一个令人迷惑的数据框,我不知道你为什么要这么做。

  

结果中的列似乎与metadir中的列匹配。

result.columns的值是从您提供的元数据中获取的,除非您要求,否则不会进行任何计算(在大多数操作中,延迟是懒惰的)。 ValueError异常不提供更多信息吗?

这是一个完整的例子

x = ({'F9_07_PZ_COMP_DIRECT': '0',
  'F9_07_PZ_DIRTRSTKEY_NAME': 'DEBRA MEALY',
  'F9_07_PZ_COMP_OTHER': '0',
  'F9_07_PZ_COMP_RELATED': '0',
  'F9_07_PZ_TITLE': 'CHAIR PERSON',
  'F9_07_PZ_AVE_HOURS_WEEK': '1.00',
  'F9_07_PC_TRUSTEE_INDIVIDUAL': 'X'},
 {'F9_07_PZ_COMP_DIRECT': '0',
  'F9_07_PZ_DIRTRSTKEY_NAME': 'HELEN GORDON',
  'F9_07_PZ_COMP_OTHER': '0',
  'F9_07_PZ_COMP_RELATED': '0',
  'F9_07_PZ_TITLE': 'VICE CHAIR',
  'F9_07_PZ_AVE_HOURS_WEEK': '1.00',
  'F9_07_PC_TRUSTEE_INDIVIDUAL': 'X'})
df = pd.DataFrame({'a': x})
d = dd.from_pandas(df, 1)
meta = pd.DataFrame(columns=['F9_07_PZ_COMP_DIRECT', 
       'F9_07_PZ_DIRTRSTKEY_NAME',
       'F9_07_PZ_COMP_OTHER', 'F9_07_PZ_COMP_RELATED', 'F9_07_PZ_TITLE',
       'F9_07_PZ_AVE_HOURS_WEEK', 'F9_07_PC_TRUSTEE_INDIVIDUAL'], dtype="O")
d.map_partitions(lambda df: df.a.apply(pd.Series), meta=meta).compute()

我怎么知道要使用什么meta?我将此功能应用于了熊猫数据框-您可能会使用一小部分数据框来做到这一点。

一些附加说明:

  • 使用熊猫加载数据,传递给dask worker然后将整个结果收集回熊猫(在内存中)数据帧是一种反模式,您不太可能看到这种方式的加速,并且可能会导致很多开销。最好使用dd.read_csv之类的东西进行加载,并且最好使用速写函数进行聚合或编写。只有compute()会很小或不会返回任何东西(因为它涉及写入输出)。官方示例不使用from_pandas。
  • string和dict处理是python方法,因此拥有任何python函数的解释器锁(GIL):线程实际上不会并行运行。要获得并行性,您需要在进程中运行,使用https://docs.dask.org/en/latest/setup/single-distributed.html
  • 最容易实现
  • 分布式调度程序还使您可以访问仪表板,该仪表板具有许多有用的信息来诊断系统的运行方式。如果您有需要遵守的防火墙规则,还可以对其行为进行很多配置。

答案 1 :(得分:0)

鉴于中小型数据集,普通的PANDAS解决方案可以工作:

df = pd.concat([df.drop(['Form990PartVIISectionAGrp'], axis=1), df['Form990PartVIISectionAGrp'].apply(pd.Series)], axis=1)

但是,具有1600万行的PANDAS解决方案将无法在具有16GB RAM的Macbook或96GB Windows机器上运行。因此,我看了达斯克。但是,如上面的答案和评论所示,Dask解决方案不起作用,因为我的数据集中的每个观察结果不一定都具有所有字典键。总计, Form990PartVIISectionAGrp 的1600万个观察值具有以下列表中的15个键:

  newkeys = ['F9_07_PC_TRUSTEE_INDIVIDUAL',
 'F9_07_PZ_DIRTRSTKEY_NAME',
 'F9_07_PZ_COMP_OTHER',
 'F9_07_PZ_COMP_RELATED',
 'F9_07_PZ_TITLE',
 'F9_07_PZ_AVE_HOURS_WEEK',
 'F9_07_PZ_COMP_DIRECT',
 'F9_07_PZ_AVE_HOURS_WEEK_RELATED',
 'F9_07_PC_OFFICER',
 'F9_07_PC_HIGH_COMP_EMPLOYEE',
 'BusinessName',
 'F9_07_PC_KEY_EMPLOYEE',
 'F9_07_PC_TRUSTEE_INSTITUTIONAL',
 'NameBusiness',
 'F9_07_PC_FORMER']

因此,我的解决方案涉及采取上述@mdurant提供的一些提示,并首先在每行中添加所有缺少的键:

for index, row in df[:].iterrows():
    for k in newkeys:
        row['Form990PartVIISectionAGrp'].setdefault(k, np.nan)

在Macbook上花了100分钟。根据mdurant的评论,然后将数据框保存为JSON格式:

df.to_json('df.json', orient='records', lines=True)

然后将文件作为文本读入Dask:

import json
import dask.bag as db
b = db.read_text('df.json').map(json.loads)

然后创建一个将列展平的函数:

def flatten(record):
    return {
    'F9_07_PZ_COMP_OTHER': record['Form990PartVIISectionAGrp']['F9_07_PZ_COMP_OTHER'],
    'F9_07_PZ_COMP_RELATED': record['Form990PartVIISectionAGrp']['F9_07_PZ_COMP_RELATED'],
    'F9_07_PC_TRUSTEE_INDIVIDUAL': record['Form990PartVIISectionAGrp']['F9_07_PC_TRUSTEE_INDIVIDUAL'],
    'F9_07_PZ_DIRTRSTKEY_NAME': record['Form990PartVIISectionAGrp']['F9_07_PZ_DIRTRSTKEY_NAME'],
    'F9_07_PZ_COMP_DIRECT': record['Form990PartVIISectionAGrp']['F9_07_PZ_COMP_DIRECT'],
    'F9_07_PZ_COMP_OTHER': record['Form990PartVIISectionAGrp']['F9_07_PZ_COMP_OTHER'],  
    'BusinessName': record['Form990PartVIISectionAGrp']['BusinessName'],  
    'F9_07_PC_FORMER': record['Form990PartVIISectionAGrp']['F9_07_PC_FORMER'],
    'F9_07_PC_HIGH_COMP_EMPLOYEE': record['Form990PartVIISectionAGrp']['F9_07_PC_HIGH_COMP_EMPLOYEE'],
    'F9_07_PC_KEY_EMPLOYEE': record['Form990PartVIISectionAGrp']['F9_07_PC_KEY_EMPLOYEE'],
    'F9_07_PC_OFFICER': record['Form990PartVIISectionAGrp']['F9_07_PC_OFFICER'],
    'F9_07_PC_TRUSTEE_INSTITUTIONAL': record['Form990PartVIISectionAGrp']['F9_07_PC_TRUSTEE_INSTITUTIONAL'],
    'F9_07_PZ_AVE_HOURS_WEEK': record['Form990PartVIISectionAGrp']['F9_07_PZ_AVE_HOURS_WEEK'],
    'F9_07_PZ_AVE_HOURS_WEEK_RELATED': record['Form990PartVIISectionAGrp']['F9_07_PZ_AVE_HOURS_WEEK_RELATED'],
    'F9_07_PZ_TITLE': record['Form990PartVIISectionAGrp']['F9_07_PZ_TITLE'],
    'NameBusiness': record['Form990PartVIISectionAGrp']['NameBusiness'],
    'URL': record['URL'],
}

然后我可以应用该功能:

df = b.map(flatten).to_dataframe()

并将数据导出到CSV:

df.to_csv('compensation*.csv')

这就像一种魅力!简而言之,根据上面mdurant的有用评论,关键是1)将缺失的关键添加到所有观察值中; 2)不将数据从PANDAS读入Dask(改用文本或CSV)。照顾好这两个问题可以很好地解决这个问题。