【问题标题】:How to remove curly braces, apostrophes and square brackets from dictionaries in a Pandas dataframe (Python)如何从 Pandas 数据框中的字典中删除花括号、撇号和方括号(Python)
【发布时间】:2017-02-17 02:30:00
【问题描述】:

我在 csv 文件中有以下数据:

from StringIO import StringIO
import pandas as pd

the_data = """
ABC,2016-6-9 0:00,95,{'//PurpleCar': [115L], '//YellowCar': [403L], '//BlueCar': [16L], '//WhiteCar-XYZ': [0L]}
ABC,2016-6-10 0:00,0,{'//PurpleCar': [219L], '//YellowCar': [381L], '//BlueCar': [90L], '//WhiteCar-XYZ': [0L]}
ABC,2016-6-11 0:00,0,{'//PurpleCar': [817L], '//YellowCar': [21L], '//BlueCar': [31L], '//WhiteCar-XYZ': [0L]}
ABC,2016-6-12 0:00,0,{'//PurpleCar': [80L], '//YellowCar': [2011L], '//BlueCar': [8888L], '//WhiteCar-XYZ': [0L]}
ABC,2016-6-13 0:00,0,{'//PurpleCar': [32L], '//YellowCar': [15L], '//BlueCar': [4L], '//WhiteCar-XYZ': [0L]}
DEF,2016-6-16 0:00,0,{'//PurpleCar': [32L], '//BlackCar': [15L], '//PinkCar': [4L], '//NPO-GreenCar': [0L]}
DEF,2016-6-17 0:00,0,{'//PurpleCar': [32L], '//BlackCar': [15L], '//PinkCar': [4L], '//NPO-GreenCar': [0L]}
DEF,2016-6-18 0:00,0,{'//PurpleCar': [32L], '//BlackCar': [15L], '//PinkCar': [4L], '//NPO-GreenCar': [0L]}
DEF,2016-6-19 0:00,0,{'//PurpleCar': [32L], '//BlackCar': [15L], '//PinkCar': [4L], '//NPO-GreenCar': [0L]}
DEF,2016-6-20 0:00,0,{'//PurpleCar': [32L], '//BlackCar': [15L], '//PinkCar': [4L], '//NPO-GreenCar': [0L]}
"""

我将文件读入Pandas数据框,如下:

df = pd.read_csv(StringIO(the_data), sep=',')

然后,我添加几个列标题,如下:

df.columns = ['Company',
                    'Date',
                    'Volume',
                    'Car1',
                    'Car2',
                    'Car3',
                    'Car4']

我看到数据是这样通过的:

ABC,2016-6-9 0:00,95,{'//PurpleCar': [115L], '//YellowCar': [403L], '//BlueCar': [16L], '//WhiteCar-XYZ': [0L]

但是,我希望看到没有以下任何一项的数据:

a) 字典开头的大括号 ("{") 和末尾的大括号 ("}")

b) 数值后的“L”

c) 数值周围的方括号("[""]"

d) 键周围的撇号

理想情况下,数据将按如下方式转换:

ABC,2016-6-9 0:00,95,//PurpleCar: 115, //YellowCar: 403, //BlueCar: 16, //WhiteCar-XYZ: 0

我试过了:

df['Car1'] = df['Car1'].str.strip(['{', '}', '[', 'L]'])

但是,它不起作用。它导致“Car1”列变为 NaN 值。

是否可以转换数据框,使数据框的每一行读取如下?

ABC,2016-6-9 0:00,95,//PurpleCar: 115, //YellowCar: 403, //BlueCar: 16, //WhiteCar-XYZ: 0

谢谢!

更新

使用以下正则表达式:

df['Car1'] = df['Car1'].str.replace(r'\D+', '').astype('int')

结果如下:

ABC,2016-6-9 0:00,95, 115 , //YellowCar: 403, //BlueCar: 16, //WhiteCar-XYZ: 0

我们丢失了“//PurpleCar”,只剩下数值 115。这是一个好的开始,但如果我们也能看到“//PurpleCar”键,那就太好了。

有什么想法吗?


更新 2:

基于 piRSquared 和 HYRY 的 cmets,我的目标是能够绘制数值结果。所以,我想让数据框看起来如下:

   Company   Date            PurpleCar  YellowCar   BlueCar     WhiteCar      

0  ABC       2016-6-9 0:00   115        403         16          0
1  ABC       2016-6-10 0:00  219        381         90          0
2  ABC       2016-6-11 0:00  817        21          31          0
3  ABC       2016-6-12 0:00  80         2011        8888        0
4  ABC       2016-6-13 0:00  32         15          4           0
5  DEF       2016-6-16 0:00  32         15          4           0
6  DEF       2016-6-17 0:00  32         15          4           0
7  DEF       2016-6-18 0:00  32         15          4           0
8  DEF       2016-6-19 0:00  32         15          4           0
9  DEF       2016-6-20 0:00  32         15          4           0

* 更新 3:*

最初发布的数据有一个小错误。这是数据:

the_data = """
ABC,2016-6-9 0:00,95,"{'//Purple': [115L], '//Yellow': [403L], '//Blue': [16L], '//White-XYZ': [0L]}"
ABC,2016-6-10 0:00,0,"{'//Purple': [219L], '//Yellow': [381L], '//Blue': [90L], '//White-XYZ': [0L]}"
ABC,2016-6-11 0:00,0,"{'//Purple': [817L], '//Yellow': [21L], '//Blue': [31L], '//White-XYZ': [0L]}"
ABC,2016-6-12 0:00,0,"{'//Purple': [80L], '//Yellow': [2011L], '//Blue': [8888L], '//White-XYZ': [0L]}"
ABC,2016-6-13 0:00,0,"{'//Purple': [32L], '//Yellow': [15L], '//Blue': [4L], '//White-XYZ': [0L]}"
DEF,2016-6-16 0:00,0,"{'//Purple': [32L], '//Black': [15L], '//Pink': [4L], '//NPO-Green': [3L]}"
DEF,2016-6-17 0:00,0,"{'//Purple': [32L], '//Black': [15L], '//Pink': [4L], '//NPO-Green': [0L]}"
DEF,2016-6-18 0:00,0,"{'//Purple': [32L], '//Black': [15L], '//Pink': [4L], '//NPO-Green': [7L]}"
DEF,2016-6-19 0:00,0,"{'//Purple': [32L], '//Black': [15L], '//Pink': [4L], '//NPO-Green': [14L]}"
DEF,2016-6-20 0:00,0,"{'//Purple': [32L], '//Black': [15L], '//Pink': [4L], '//NPO-Green': [21L]}"
"""

此数据与原始数据的区别在于左大括号 ("{") 之前和右大括号 ("}") 之后的撇号 (")

【问题讨论】:

  • 我提供的数据是字典的输出。此外,它不是 JSON。
  • 我认为您遇到的问题是您的 CSV 文件无效。它使用逗号来分隔字段,并分隔构成最后一个值的字典的键/值对。也许您可以预处理文本以转义内部逗号,以便 Pandas 可以更整齐地解析它?

标签: python regex pandas


【解决方案1】:

编辑:该文件似乎实际上是一个转义的 CSV,因此我们不需要对此部分进行自定义解析。

正如@Blckknght 在评论中指出的那样,该文件不是有效的 CSV。我会在我的回答中做一些假设。他们是

  1. 您无法控制数据,因此无法正确转义逗号。
  2. 前三列不包含任何逗号。
  3. 第三列遵循 python dict 的语法。
  4. 列表中始终有一个值位于 dict 值中。

首先,一些导入

import ast
import pandas as pd

我们只需用逗号分隔行,因为我们不需要处理任何类型的 CSV 转义(假设 #1 和 #2)。

rows = (line.split(",", 3) for line in the_data.splitlines() if line.strip() != "")

fixed_columns = pd.DataFrame.from_records(rows, columns=["Company", "Date", "Value", "Cars_str"])

fixed_columns = pd.read_csv(..., names=["Company", "Date", "Value", "Cars_str"])

前三列是固定的,我们保持原样。最后一列我们可以用ast.literal_eval 解析,因为它是dict(假设#3)。如果格式比正则表达式发生变化,这在 IMO 上更具可读性和灵活性。您还可以更早地检测到格式更改。

cars = fixed_columns["Cars_str"].apply(ast.literal_eval)
del fixed_columns["Cars_str"]

这部分的答案是your other question

我们准备函数来处理字典的键和值,这样如果我们对字典内容的假设失败,它们就会失败。

def get_single_item(list_that_always_has_single_item):
    v, = list_that_always_has_single_item
    return v

def extract_car_name(car_str):
    assert car_str.startswith("//"), car_str
    return car_str[2:]

我们应用函数并构造pd.Series,这使我们能够...

dynamic_columns = cars.apply(
    lambda x: pd.Series({
            extract_car_name(k): get_single_item(v) 
            for k, v in x.items()
    }))    

...将列添加到数据框

result = pd.concat([fixed_columns, dynamic_columns], axis=1)
result

最后,我们得到了表格:

  Company            Date Value  BlackCar  BlueCar  NPO-GreenCar  PinkCar  \
0     ABC   2016-6-9 0:00    95       NaN     16.0           NaN      NaN   
1     ABC  2016-6-10 0:00     0       NaN     90.0           NaN      NaN   
2     ABC  2016-6-11 0:00     0       NaN     31.0           NaN      NaN   
3     ABC  2016-6-12 0:00     0       NaN   8888.0           NaN      NaN   
4     ABC  2016-6-13 0:00     0       NaN      4.0           NaN      NaN   
5     DEF  2016-6-16 0:00     0      15.0      NaN           0.0      4.0   
6     DEF  2016-6-17 0:00     0      15.0      NaN           0.0      4.0   
7     DEF  2016-6-18 0:00     0      15.0      NaN           0.0      4.0   
8     DEF  2016-6-19 0:00     0      15.0      NaN           0.0      4.0   
9     DEF  2016-6-20 0:00     0      15.0      NaN           0.0      4.0   

   PurpleCar  WhiteCar-XYZ  YellowCar  
0      115.0           0.0      403.0  
1      219.0           0.0      381.0  
2      817.0           0.0       21.0  
3       80.0           0.0     2011.0  
4       32.0           0.0       15.0  
5       32.0           NaN        NaN  
6       32.0           NaN        NaN  
7       32.0           NaN        NaN  
8       32.0           NaN        NaN  
9       32.0           NaN        NaN  

【讨论】:

  • 此解决方案适用于我在示例中提供的数据。我刚刚检查了实际数据,在每行的左大括号之前有一个撇号",在右大括号之后有一个撇号"。这导致我得到错误:AttributeError: 'str' object has no attribute 'items' 当我打电话给dynamic_columns = adaptors.apply( lambda x: pd.Series({ extract_adaptor_name(k): get_single_item(v) for k, v in x.items() }))...知道如何解决这个问题吗?谢谢!
  • 然后看起来文件实际上是一个 CSV。而不是开头的特殊解析(line.split),直接使用pd.read_csv即可。
【解决方案2】:

我认为最好将字符串转换成两列:

from io import StringIO
import pandas as pd


df = pd.read_csv(StringIO(the_data), sep=',', header=None)
df.columns = ['Company','Date','Volume','Car1','Car2','Car3','Car4']

cars = ["Car1", "Car2", "Car3", "Car4"]
pattern = r"//(?P<color>.+?)':.*?(?P<value>\d+)"
df2 = pd.concat([df[col].str
                    .extract(pattern)
                    .assign(value=lambda self: pd.to_numeric(self["value"]))
                    for col in cars],
                axis=1, keys=cars)

结果:

        Car1             Car2           Car3                Car4      
       color value      color value    color value         color value
0  PurpleCar   115  YellowCar   403  BlueCar    16  WhiteCar-XYZ     0
1  PurpleCar   219  YellowCar   381  BlueCar    90  WhiteCar-XYZ     0
2  PurpleCar   817  YellowCar    21  BlueCar    31  WhiteCar-XYZ     0
3  PurpleCar    80  YellowCar  2011  BlueCar  8888  WhiteCar-XYZ     0
4  PurpleCar    32  YellowCar    15  BlueCar     4  WhiteCar-XYZ     0
5  PurpleCar    32   BlackCar    15  PinkCar     4  NPO-GreenCar     0
6  PurpleCar    32   BlackCar    15  PinkCar     4  NPO-GreenCar     0
7  PurpleCar    32   BlackCar    15  PinkCar     4  NPO-GreenCar     0
8  PurpleCar    32   BlackCar    15  PinkCar     4  NPO-GreenCar     0
9  PurpleCar    32   BlackCar    15  PinkCar     4  NPO-GreenCar     0

【讨论】:

    【解决方案3】:

    这应该可以解决问题

    s = pd.read_csv(StringIO(the_data), sep='|', header=None, squeeze=True)
    
    left = s.str.split(',').str[:3].apply(pd.Series)
    left.columns = ['Company', 'Date', 'Volume']
    
    right = s.str.split(',').str[3:].str.join(',') \
             .str.replace(r'[\[\]\{\}\']', '') \
             .str.replace(r'(:\s+\d+)L', r'\1') \
             .str.split(',', expand=True)
    right.columns = ['Car{}'.format(i) for i in range(1, 5)]
    
    pd.concat([left, right], axis=1)
    

    【讨论】:

    • 是否可以在实施您的解决方案的同时保留 df2 中的“公司”、“日期”和“值”列?谢谢!
    猜你喜欢
    • 1970-01-01
    • 2022-11-16
    • 1970-01-01
    • 2020-08-05
    • 2021-10-09
    • 2022-12-23
    • 2021-11-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多