使用Pandas解析漂亮打印的表格数据

6

什么是复制包含不同分隔符、列名中有空格等内容的表格的最佳方法?函数pd.read_clipboard()不能单独完成此任务。

示例1:

| Age Category | A | B  | C  | D |
|--------------|---|----|----|---|
| 21-26        | 2 | 2  | 4  | 1 |
| 26-31        | 7 | 11 | 12 | 5 |
| 31-36        | 3 | 5  | 5  | 2 |
| 36-41        | 2 | 4  | 1  | 7 |
| 41-46        | 0 | 1  | 3  | 2 |
| 46-51        | 0 | 0  | 2  | 3 |

预期结果:

 Age Category  A  B   C   D    
 21-26         2  2   4   1 
 26-31         7  11  12  5 
 31-36         3  5   5   2 
 36-41         2  4   1   7 
 41-46         0  1   3   2 
 46-51         0  0   2   3

编辑:

示例 2:

+---+---------+--------+
| id|firstName|lastName|
+---+---------+--------+
|  1|     Mark|   Brown|
|  2|      Tom|Anderson|
|  3|   Joshua|Peterson|
+---+---------+--------+

预期结果:

   id firstName  lastName
0   1      Mark     Brown
1   2       Tom  Anderson
2   3    Joshua  Peterson

我寻求一种通用方法,可应用于最常见的表格类型。


@ldz 不行。这个表格有问题。 - Mykola Zotko
我看过别人使用StringIO,但我也很好奇。谢谢这个好问题。 - Umar.H
2
@Datanovice pd.read_clipboard('|').dropna(how='all',axis=1).iloc[1:].reset_index(drop=True) - anky
1
感谢@anky_91,代码并不是问题,我想知道是否有更好的方法来处理这样的字符串表,而无需编写上述代码,但正如jorijnsmit所指出的那样,它更多是为了视觉表示。 - Umar.H
4个回答

7

以下是使用re.subio.StringIO 的另一种潜在解决方案:

from io import StringIO
import re

text1 = """
| Age Category | A | B  | C  | D |
|--------------|---|----|----|---|
| 21-26        | 2 | 2  | 4  | 1 |
| 26-31        | 7 | 11 | 12 | 5 |
| 31-36        | 3 | 5  | 5  | 2 |
| 36-41        | 2 | 4  | 1  | 7 |
| 41-46        | 0 | 1  | 3  | 2 |
| 46-51        | 0 | 0  | 2  | 3 |
"""

text2= """
+---+---------+--------+
| id|firstName|lastName|
+---+---------+--------+
|  1|     Mark|   Brown|
|  2|      Tom|Anderson|
|  3|   Joshua|Peterson|
+---+---------+--------+
"""

df1 = pd.read_csv(StringIO(re.sub(r'[|+]|-{2,}', '  ', text1)), sep='\s{2,}', engine='python')
df2 = pd.read_csv(StringIO(re.sub(r'[|+]|-{2,}', '  ', text2)), sep='\s{2,}', engine='python')

[out]

df1

  Age Category  A   B   C  D
0        21-26  2   2   4  1
1        26-31  7  11  12  5
2        31-36  3   5   5  2
3        36-41  2   4   1  7
4        41-46  0   1   3  2
5        46-51  0   0   2  3

df2

   id firstName  lastName
0   1      Mark     Brown
1   2       Tom  Anderson
2   3    Joshua  Peterson

7
这么复杂的原因是这种 ASCII 表格并不是为数据传输而设计的,它们真正的作用是以视觉上令人愉悦的方式呈现数据。
这并不意味着不能将其用于转换为 pandas!让我们从 .read_clipboard() 开始:
df = pd.read_clipboard(sep='|').iloc[1:,1:-1]

我们需要定义分隔符,而不是使用逗号作为默认分隔符。我们将使用|作为分隔符。

.iloc[1:,1:-1]会去掉第一行(-----------)以及第一列和最后一列:由于每行开头和结尾都有一条后续的|,所以pandas会将其看作“空”列。

现在,我们只需要剥离列名和值中的空格即可:

stripped_columns = []
for column_name in df.columns:
    df[column_name] = df[column_name].str.strip()
    stripped_columns.append(column_name.strip())
df.columns = stripped_columns

如果你希望年龄类别成为你的索引:

df.set_index('年龄类别', inplace=True)

最后一步是确保所有列中现在都实际保存数字而不是字符串:

df = df.astype('int')

导致:
<class 'pandas.core.frame.DataFrame'>
Index: 6 entries, 21-26 to 46-51
Data columns (total 4 columns):
A    6 non-null int64
B    6 non-null int64
C    6 non-null int64
D    6 non-null int64
dtypes: int64(4)
memory usage: 400.0+ bytes

我不确定你从剪贴板读取内容的原因是什么。更加优雅的解决方案可能是将其粘贴到一个.csv文件中,然后使用更高级的功能.read_csv()所提供的。然而,必要的转换仍将保持不变。


为什么“----”在所有表格中都很常见,但是如果没有进行额外的处理,它既不能被SQL直接读取,也不能被Python直接读取? - Umar.H
1
我认为这是因为它的真正目的不是将数据从一个环境转移到另一个环境,而是为其提供视觉描绘。 - gosuto

4

一种选择是咬紧牙关,对数据进行预处理。这并不太糟糕,因为在其参数中,pd.read_csv 只能处理有限的情况,如果你想详尽处理你所需要的情况,最终你将不得不使用正则表达式。

为了处理大多数常见的漂亮打印表格,我会编写一个循环来过滤/替换行中的字符,然后使用比较简单的 read_csv 调用读入输出。

import os 

def load(filename):
    with open(filename) as fin, open('temp.txt', 'w') as fout:
        for line in fin:
            if not line.strip()[:2] in {'|-', '+-'}: # filter step
                fout.write(line.strip().strip('|').replace('|', ',')+'\n')

    df = pd.read_csv('temp.txt', sep=r'\s*,\s*', engine='python')
    os.unlink('temp.txt') # cleanup

    return df

df1 = load('data1.txt')
df2 = load('data2.txt')

df1

  Age Category  A   B   C
0        21-26  2   2   4
1        26-31  7  11  12
2        31-36  3   5   5
3        36-41  2   4   1
4        41-46  0   1   3
5        46-51  0   0   2

df2

   id firstName  lastName
0   1      Mark     Brown
1   2       Tom  Anderson
2   3    Joshua  Peterson

@Datanovice 不用担心。你是不是想把悬赏给另一个答案?我无所谓,只是好奇。我知道这不是完美的答案,但我认为这个问题本身就没有完美的答案。 - cs95
如果我造成了任何冒犯,我向您表示真诚的道歉。我的意图是想要更多地了解,您的答案是正确的,但我想知道为什么会存在这些类型的表格,或者是否有一种我不知道的方法来读取它们。 - Umar.H
1
@Datanovice 关于这些表为什么存在,它们很可能是SQL或Spark表转储,或者它们是tabulate漂亮打印的表格 - cs95

3
对于这种类型的表格,您可以简单地使用:
df = pd.read_clipboard(sep='|')

之后只需要进行最小限度的清理即可:

df = df.drop(0)
df = df.drop(['Unnamed: 0','Unnamed: 6'], axis=1)

关于"编写电子表格"的问题...我认为没有比简单表现更方便的东西,但是这里提供了以上清理后的代码,虽然代码很糟糕: df
df1 = df.append(pd.DataFrame({i:['-'*len(i)] for i in df.columns})).sort_index() #adding the separator to column titles
df2 = pd.DataFrame({str(i)+'|':['|']*len(df1) for i in range(len(df1.columns))})
df3 = df1.join(df2)
col_order = [j for i in [[df1.columns[x], df2.columns[x]] for x in range(len(df1.columns))] for j in i]
df3.index = ['|']*len(df3.index)

然后:

df3[col_order]

    Age Category  0|   A  1|   B   2|   C   3|   D  4|
|  --------------  |  ---  |  ----  |  ----  |  ---  |
|   21-26          |   2   |   2    |   4    |   1   |
|   26-31          |   7   |   11   |   12   |   5   |
|   31-36          |   3   |   5    |   5    |   2   |
|   36-41          |   2   |   4    |   1    |   7   |
|   41-46          |   0   |   1    |   3    |   2   |
|   46-51          |   0   |   0    |   2    |   3   |

(edited)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接