Магия панд с уродливым форматом CSV

Древнее программное обеспечение для моделирования атомов создает действительно уродливый файл CSV, который я хочу импортировать в фрейм данных pandas. Формат выглядит так:

ITEM: TIMESTEP
0
ITEM: NUMBER OF ATOMS
491
ITEM: BOX BOUNDS pp pp pp
0.0000000000000000e+00 2.8000000000000000e+01
0.0000000000000000e+00 2.8000000000000000e+01
0.0000000000000000e+00 2.8000000000000000e+01
ITEM: ATOMS id type xs ys zs
1 1 0 0 0.142857
2 1 0.0714286 0.0714286 0.142857
3 1 0.0714286 0 0.214286
4 1 0 0.0714286 0.214286
...
491 1 2 2.3 0.4
ITEM: TIMESTEP
0
ITEM: NUMBER OF ATOMS
491
ITEM: BOX BOUNDS pp pp pp
0.0000000000000000e+00 2.8000000000000000e+01
0.0000000000000000e+00 2.8000000000000000e+01
0.0000000000000000e+00 2.8000000000000000e+01
ITEM: ATOMS id type xs ys zs
1 1 0 0 0.142857
2 1 0.0714286 0.0714286 0.142857
3 1 0.0714286 0 0.214286
4 1 0 0.0714286 0.214286
...
491 215 0.4 12.4 2.4
...
...
ITEM: TIMESTEP
1002
...

В основном это повторяющийся заголовок с информацией о номере итерации. Мне кажется, что самый простой способ превратить это в панд - это df с переменными ['id', 'type', 'xs', 'ys', 'zs'] и добавить к нему новый столбец "TIMESTEP" , так что это будет хороший 2D df. В качестве альтернативы может быть массив с несколькими индексами Timestep 1 -> internal_df['id', 'type', 'xs', 'ys', 'zs']

Информационные строки (1-9) можно удалить.

Конечный результат в идеале должен выглядеть так:

    Index   a   b           c           d           TIMESTEP
    1       1   0           0           0.142857    0
    2       1   0.0714286   0.0714286   0.142857    0
    3       1   0.0714286   0           0.214286    0
    4       1   0           0.0714286   0.214286    0
    5       1   0.142857    0           0.142857    0
    ...
    474     1   0.78636     0.788005    0.425791    100002

Не могли бы вы предложить сценарий форматирования строк (пример приветствуется), или, может быть, Pandas read_csv с умным набором настроек может сделать это из коробки?

EDITED: Добавлен истинный бит INFORMATION заголовка, который должен быть отброшен (бит от «Количества записей» до строки «Переменные»)

Как должен выглядеть ваш красивый 2D df? Можете ли вы добавить это к своему вопросу, пожалуйста?

Timeless 01.02.2023 12:16

Почему 1002 находится в той же строке, что и TIMESTEP?

Corralien 01.02.2023 12:17

Это не CSV. CSV конкретно означает Comma Separated Values, но это выглядит как многостраничный отчет, сохраненный в виде текстового файла.

Panagiotis Kanavos 01.02.2023 12:19

Предполагая, что вам не нужны никакие данные за пределами таблиц, вы можете попробовать read_table или, может быть, даже read_csv и отбросить «плохие» строки, например те, в которых слишком много NaN, чье первое значение не является числом и т. д. Если количество заголовков и строк исправлено, вы можете передать лямбду в skip_rows, которая отклоняет строки по индексу.

Panagiotis Kanavos 01.02.2023 12:25

Однако может быть лучше читать и анализировать ваш код и обрабатывать каждый возможный тип строки по-разному.

Panagiotis Kanavos 01.02.2023 12:26

Panagiotis, разделитель можно легко изменить на ' ' в pandas, но основная проблема - это повторяющийся заголовок с изменением TIMESTEP, ура.

Gedas Sarpis 01.02.2023 12:26

Это не CSV по любому определению. Проблема не в TIMESTEP, а в том, что все, что находится за пределами таблиц, не является таблицей и не может быть обработано таким образом. Даже если бы в первой строке был только один раздел с одним TIMESTEP, вы все равно не смогли бы использовать read_csv и получить TIMESTEP там.

Panagiotis Kanavos 01.02.2023 12:28

Да, формат данных действительно раздражает, отсюда и вопрос. Хаком было бы, если бы машина сохраняла каждую временную метку в отдельном CSV, тогда начальный заголовок можно было бы игнорировать, а дальше будет просто чистая таблица чисел. Я в основном удалил ненужные строки в Sublime, но это не слишком безопасно, так как теряет информацию о TIMESTEP (конечно, они идут по порядку, но я хочу это автоматизировать).

Gedas Sarpis 01.02.2023 12:33

Это похоже на смесь между строковыми записями и значениями, разделенными пробелами. Вероятно, было бы относительно просто преобразовать с помощью awk и массива 2d. Обновляйте индекс каждый раз, когда видите TIMESTEP. Затем обработайте строки, которые соответствуют шаблону для фактических данных (1 a1 b1..). В конце распечатайте массив. Конечно, вы можете сделать то же самое, используя Python.

MyICQ 01.02.2023 12:42

Предполагая, что вывод будет чем-то таким для проиллюстрированного примера

MyICQ 01.02.2023 12:58
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
10
81
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

С пандами, вот один из способов, как вы можете подойти к этому (чтобы дать вам только общую логику).

#pip install pandas
import pandas as pd 
import numpy as np
​
df = pd.read_csv(StringIO(s), sep = "/", header=None)
​
m1 = df[0].str.contains("TIMESTEP")
m2 = df[0].str.contains("Information").fillna(False)
m3 = df[0].str.contains("ITEM|Variables|\+", regex=True).fillna(False)
​
conds, vals = [m1|m1.shift(1).fillna(False), m2|m3], ["DATA", "TO_SKIP"]
​
out = (
        df
          .assign(flag= pd.Series(np.select(conds, vals, None)).bfill().ffill())
          .pivot(columns = "flag", values=0)
          .loc[:, "DATA"].dropna()
          .str.split(expand=True)
          .assign(col= lambda x: x[0].shift(-1).where(x[1].str.contains("TIMESTEP")).ffill())
          .set_axis(["Index", "a", "b", "c", "d", "TIMESTEP"], axis=1)
          .dropna(how = "any")
          .reset_index(drop=True)
        )
​

Выход :

print(out)

  Index    a          b          c         d TIMESTEP
0     1    1          0          0  0.142857        0
1     2    1  0.0714286  0.0714286  0.142857        0
2     3    1  0.0714286          0  0.214286        0
3     4    1          0  0.0714286  0.214286        0
4   491    1          2        2.3       0.4        0
5     1    1          0          0  0.142857        0
6     2    1  0.0714286  0.0714286  0.142857        0
7     3    1  0.0714286          0  0.214286        0
8     4    1          0  0.0714286  0.214286        0
9   491  215        0.4       12.4       2.4        0

Отказ от ответственности: этот подход может быть неэффективным для больших файлов.

Вы должны добавить это в свой вопрос, потому что это нечитабельно в разделе комментариев.

Timeless 01.02.2023 15:26

Отлично! Я добавил больше деталей в «Информационные строки», но пытаюсь использовать ваше решение для их удаления :) Также спасибо Timeless за предложение отредактировать вопрос OG.

Gedas Sarpis 01.02.2023 15:31

Нам просто нужно добавить |\+ в третью маску, поэтому мы пропускаем строки, содержащие символ +. Я обновил свой ответ, пожалуйста, надеюсь, это поможет;)

Timeless 01.02.2023 15:32

Я много изучаю... Что делает этот оператор регулярного выражения? Когда я пытаюсь запустить свои данные, я получаю: ValueError: Length mismatch: Expected axis has 8 elements, new values have 7 elements. Что, вероятно, означает, что у меня либо неправильное условие, либо какое-то несоответствие столбцов...

Gedas Sarpis 01.02.2023 15:47

Кажется, есть один дополнительный столбец. Можете ли вы прокомментировать (#) строку, где мы вызываем set_index, перезапустить код и поделиться результатом (в виде скриншота)?

Timeless 01.02.2023 15:50

Извините, вы имеете в виду set_axis?

Gedas Sarpis 01.02.2023 15:57

Мой плохой, да set_axis, можешь сделать это, пожалуйста?

Timeless 01.02.2023 15:59

Давайте продолжим обсуждение в чате.

Gedas Sarpis 01.02.2023 16:03

@GedasSarpis, тебе удалось сделать скриншот?

Timeless 01.02.2023 19:34

Я написал вам в чат, так как обсуждение расширяется

Gedas Sarpis 01.02.2023 19:38

Другие вопросы по теме