Python Pandas - 读取带有注释标题行的 csv

Posted

技术标签:

【中文标题】Python Pandas - 读取带有注释标题行的 csv【英文标题】:Python Pandas - Read csv with commented header line 【发布时间】:2020-12-15 04:14:46 【问题描述】:

我想用 pandas 读取和处理一个 csv 文件。该文件(如下所示)包含多个由# 标记指示的标题行。我可以通过使用轻松导入该文件

import pandas as pd

file = "data.csv"
data = pd.read_csv(file, delimiter="\s+",
                   names=["Time", "Cd", "Cs", "Cl", "CmRoll", "CmPitch", "CmYaw", "Cd(f)",
                           "Cd(r)", "Cs(f)", "Cs(r)", "Cl(f)", "Cl(r)"],
                   skiprows=13)

但是,我有很多具有不同标题名称的此类文件,我不想手动命名它们 (Time Cd Cs ...)。每个文件之间的注释行数也不同。所以我想自动化这个任务。

在将数据传递到 pandas 数据框之前,我是否必须在这里使用正则表达式之类的东西?

感谢您的建议。

是的,标题名称也以 # 开头。

data.csv:

# Force coefficients    
# dragDir               : (9.9735673312816520e-01 7.2660490528994301e-02 0.0000000000000000e+00)
# sideDir               : (0.0000000000000000e+00 0.0000000000000000e+00 -1.0000000000000002e+00)
# liftDir               : (-7.2660490528994315e-02 9.9735673312816520e-01 0.0000000000000000e+00)
# rollAxis              : (9.9735673312816520e-01 7.2660490528994301e-02 0.0000000000000000e+00)
# pitchAxis             : (0.0000000000000000e+00 0.0000000000000000e+00 -1.0000000000000002e+00)
# yawAxis               : (-7.2660490528994315e-02 9.9735673312816520e-01 0.0000000000000000e+00)
# magUInf               : 4.5000000000000000e+01
# lRef                  : 5.9399999999999997e-01
# Aref                  : 3.5639999999999999e-03
# CofR                  : (1.4999999999999999e-01 0.0000000000000000e+00 0.0000000000000000e+00)
#
# Time                      Cd                          Cs                          Cl                          CmRoll                      CmPitch                     CmYaw                       Cd(f)                       Cd(r)                       Cs(f)                       Cs(r)                       Cl(f)                       Cl(r)                   
5e-06                       1.8990180226147195e+00  1.4919925634649792e-11  2.1950119509976829e+00  -1.1085971520784955e-02 -1.0863798447281650e+00 9.5910040927874810e-03  9.3842303978657482e-01  9.6059498282814471e-01  9.5910041002474442e-03  -9.5910040853275178e-03 1.1126130770676479e-02  2.1838858202270064e+00
1e-05                       2.1428508927716594e+00  1.0045114197556737e-08  2.5051633252700962e+00  -1.2652317494411272e-02 -1.2367567798452046e+00 1.0822379290263353e-02  1.0587731288914184e+00  1.0840777638802410e+00  1.0822384312820453e-02  -1.0822374267706254e-02 1.5824882789843508e-02  2.4893384424802525e+00
...

【问题讨论】:

你的标题总是在#行的最后位置吗? 实际上大部分文件 - 所以yesno :) 编辑:是的。 【参考方案1】:

在读取文件之前提取标题怎么样? 我们只假设您的标题行以# 开头。标题的提取及其在文件中的位置是自动的。我们还确保不会读取多余的行(第一条数据行除外)。

with open(file) as f:
    line = f.readline()
    cnt = 0
    while line.startswith('#'):
        prev_line = line
        line = f.readline()
        cnt += 1
        # print(prev_line)

header = prev_line.strip().lstrip('# ').split()

df = pd.read_csv(file, delimiter="\s+",
                   names=header,
                   skiprows=cnt
           )

这样,您还可以处理其他标题行。它还为您提供文件中标题的位置。

【讨论】:

这很好用。谢谢你。但是,由于我的文件很大,我只会阅读@Manakin 提到的前 50 行 @Sunsheep,我的解决方案只读取标题行 + 第一个数据行。因此,如果您的标题是 13 行,那么您将阅读 14 行。【参考方案2】:

这应该可以,它既简单又高效,它将变量保持在最低限度,并且除了文件名之外不需要任何输入。

with open(file, 'r') as f:
    for line in f:
        if line.startswith('#'):
            header = line
        else:
            break #stop when there are no more #

header = header[1:].strip().split()

data = pd.read_csv(file, delimiter="\s+", comment='#', names=header)

您首先打开文件并只读取注释行(这将快速且节省内存)。最后一个有效行将是最终标题,它将被清理并转换为列表。最后,使用pandas.read_csv()comment='#' 打开文件,这将跳过注释行和names=header

【讨论】:

效果很好。谢谢。类似于 Stefans 的回答。他快了一点:)【参考方案3】:

一点正则表达式可能会有所帮助。

这不是最漂亮的解决方案,因此请随时发布更好的解决方案。

让我们读取任何文件的前 50 行,以找到最后一次出现的散列,它应该是列名。

^ 在行首断言位置

# 匹配字符 # 字面意思(区分大小写)


import re
n_rows = 50

path_ = 'your_file_location'

with open(path_,'r') as f:
    data = []
    for i in range(n_rows): # read only 50 rows here. 
        for line in f:
            if re.match('^#',line):
                data.append(line)

start_col = max(enumerate(data))[0]


df = pd.read_csv(path_,sep='\s+',skiprows=start_col) # use your actual delimiter.

          #      Time            Cd        Cs        Cl    CmRoll   CmPitch  \
0  0.000005  1.899018  1.491993e-11  2.195012 -0.011086 -1.086380  0.009591   
1  0.000010  2.142851  1.004511e-08  2.505163 -0.012652 -1.236757  0.010822   

      CmYaw     Cd(f)     Cd(r)     Cs(f)     Cs(r)     Cl(f)  Cl(r)  
0  0.938423  0.960595  0.009591 -0.009591  0.011126  2.183886    NaN  
1  1.058773  1.084078  0.010822 -0.010822  0.015825  2.489338    NaN  

编辑,处理列名中的#

我们可以分两步完成。

我们可以读取 0 行,但对标题列进行切片。

首先从标题行读取文件,但将header 参数设置为None,因此不会设置任何标题。

然后我们可以手动设置列标题。

df = pd.read_csv(path_,sep='\s+',skiprows=start_col + 1, header=None)
df.columns = pd.read_csv(path_,sep='\s+',skiprows=start_col,nrows=0).columns[1:]

print(df)

       Time        Cd            Cs        Cl    CmRoll   CmPitch     CmYaw  \
0  0.000005  1.899018  1.491993e-11  2.195012 -0.011086 -1.086380  0.009591   
1  0.000010  2.142851  1.004511e-08  2.505163 -0.012652 -1.236757  0.010822   

      Cd(f)     Cd(r)     Cs(f)     Cs(r)     Cl(f)     Cl(r)  
0  0.938423  0.960595  0.009591 -0.009591  0.011126  2.183886  
1  1.058773  1.084078  0.010822 -0.010822  0.015825  2.489338 

【讨论】:

这无法处理标题名称行上的 # 注释标记,该标签导致最后一列中的 NaN,因为 pandas 认为最初的 # 也是标题名称。但你的解决方案对我来说是一个好的开始。谢谢。 @Sunsheep 实际上是你的 delim '\s+' ? 是的。您的编辑效果很好,但在我看来 Stefans 的回答更加优雅 :) 但是,这些文件很大,所以您的解决方案只读取前 50 行非常方便。【参考方案4】:

为了简化它并在不使用循环的情况下节省时间,您可以为 # 注释行创建 2 个数据框,其余的。 从那些注释行中取最后一个 - 那是您的标题,然后使用 concat() 合并数据数据框和此标题,如果需要将第一行指定为标题,您可以使用 df.columns=df.iloc[0]

df = pd.DataFrame(
    'A':['#test1 : (000000)','#test1 (000000)','#test1 (000000)','#test1 (000000)','#Time (000000)','5e-06','1e-05'],
)
print(df)
   

                A
0  #test1 : (000000)
1    #test1 (000000)
2    #test1 (000000)
3    #test1 (000000)
4     #Time (000000)
5              5e-06
6              1e-05

df_header = df[df.A.str.contains('^#')]
print(df_header)
         

          A
0  #test1 : (000000)
1    #test1 (000000)
2    #test1 (000000)
3    #test1 (000000)
4     #Time (000000)
df_data = df[~df.A.str.contains('^#')]
print(df_data)
       A
5  5e-06
6  1e-05

df = (pd.concat([df_header.iloc[[-1]],df_data])).reset_index(drop=True)
df.A=df.A.str.replace(r'^#',"")



print(df)
          

     A
0  Time (000000)
1          5e-06
2          1e-05

【讨论】:

【参考方案5】:

假设 cmets 总是以单个 '#' 开头,并且标题位于最后一个注释行中:

import csv

def read_comments(csv_file):
    for row in csv_file:
        if row[0] == '#':
            yield row.split('#')[1].strip()

def get_last_commented_line(filename):
    with open(filename, 'r', newline='') as f:
        decommented_lines = [line for line in csv.reader(read_comments(f))]
        header = decommented_lines[-1]
        skiprows = len(decommented_lines)
        return header, skiprows

header, skiprows = get_last_commented_line(path)
pd.read_csv(path, names=header, skiprows=skiprows)

【讨论】:

【参考方案6】:
# Read the lines in file
with open(file) as f:
    lines = f.readlines()

# Last commented line is header
header = [line for line in lines if line.startswith('#')][-1]

# Strip line and remove '#' 
header = header[1:].strip().split()

df = pd.read_csv(file, delimiter="\s+", names=header, comment='#')

【讨论】:

以上是关于Python Pandas - 读取带有注释标题行的 csv的主要内容,如果未能解决你的问题,请参考以下文章

使用 numpy / pandas 读取 Python 中 CSV 文件的最后 N 行

使用 numpy / pandas 读取 Python 中 CSV 文件的最后 N 行

使用 pandas 读取带有 numpy 数组的 csv

使用 pandas 读取带有 numpy 数组的 csv

有没有办法使用 Python Pandas 读取所有行直到遇到空行

pandas读取txt---按行输入按行输出