Python Pandas — чтение csv с прокомментированной строкой заголовка

#python #pandas #csv #parsing #openfoam

#python #pandas #csv #синтаксический анализ #openfoam

Вопрос:

Я хочу прочитать и обработать файл CSV с помощью pandas. Файл (как показано ниже) содержит несколько строк заголовка, которые обозначены # тегом. Я могу легко импортировать этот файл, используя

 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 dataframe?

Спасибо за любой совет.

И да, имена заголовков также начинаются с # .

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
...
  

Комментарии:

1. ваш заголовок всегда находится в последней позиции # lines?

2. На самом деле большинство файлов — так что да 🙂 РЕДАКТИРОВАТЬ: Да, это так.

Ответ №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
           )
  

С помощью этого вы также можете обрабатывать другие строки заголовка. Это также дает вам положение заголовка в файле.

Комментарии:

1. Это работает хорошо. Спасибо. Однако, поскольку мои файлы слишком большие, я буду читать только первые 50 строк, как указано @Manakin

2. @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() with comment='#' , который пропустит прокомментированные строки, и names=header .

Комментарии:

1. Работает хорошо. Спасибо. Похоже на ответ Стефана. Он был немного быстрее 🙂

Ответ №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 
  

Комментарии:

1. Это не может обработать # тег комментария в строке имени заголовка, который приводит к NaN в последнем столбце, потому что pandas считает, что инициал # тоже является именем заголовка. Но ваше решение — хорошее начало для меня. Спасибо.

2. @Sunsheep это ваш разделитель на самом деле 's ' ?

3. ДА. Ваше редактирование работает хорошо, но, на мой взгляд, ответ Стефана немного более элегантный 🙂 Однако эти файлы слишком большие, поэтому ваше решение с чтением только первых 50 строк довольно удобно.

Ответ №4:

Чтобы упростить это и сэкономить время без использования циклов, вы можете создать 2 фрейма данных для # прокомментированных строк и всего остального. Из этих прокомментированных строк возьмите последнюю — это ваш заголовок, а затем объедините data dataframe и этот заголовок, используя 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:

Предполагается, что комментарии всегда начинаются с одного ‘#’, а заголовок находится в последней прокомментированной строке:

 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='#')