Парсинг yaml-файлов в таблицу

Есть несколько yaml-файлов:

файл 1

 phone:
   os: android
   size:
     length: 80
     height: 10
     weight: 40
   model: SE-35
   price: 20000

файл 2

 phone:
   os: apple
   model: Iphone 15
   price: 150000
   color: gold

файл 3

 phone:
   os: apple
   model: Iphone 15
   color: black
   exist: absent

файл 4

 phone:
   os: android
   size:
     length: 75
     height: 8
     weight: 36
   camera:
     front: 4 mp
     back: 64 mp
       zoom: 3
   model: SE-35
   price: 20000

Надо их представить в табличном виде, где первый столбец это ключ, а остальные - это значения.

Выглядеть таблица будет примерно так:

Phone file1 file2 file3 file4
phone.os android apple apple android
phone.size.length 80 75
phone.size.height 10 8
phone.size.weight 40 35
phone.model SE-35 Iphone 15 Iphone 15
phone.price 20000 150000 20000
phone.color Gold Black
phone.exist absent
phone.camera.front 4 mp
phone.camera.back 64 mp
phone.camera.back.zoom 3

На самом деле файлов таких много, я лишь привел некоторые примеры из них

пытаюсь их все распарсить: вот код:

import os
import yaml
import pandas as pd

# получаем данные из файла
def GetData(data, prefix):
  if isinstance(data, dict):
    for k, v in data.items():
        yield from GetData(v, f'{prefix}/{k}')
  else:
    yield (prefix, data)

# находим все файлы в директории
def findYamlFiles(directory):
  for root, dirs, files in os.walk(directory):
    for file in files:
        if file.endswith('.yaml' or '.yml'):
            with open(os.path.join(root, file), 'r') as f:
              yaml_data = yaml.safe_load(f)
              df = pd.DataFrame(GetData(yaml_data, ''), columns=['phone', 'rezults'])

path = "~/phones"
findYamlFiles(path)

Итого в df я получаю полностью распарсенный файл yaml, состоящий из двух столбцов, но собрать все df в одну таблицу у меня не получается.


Ответы (3 шт):

Автор решения: Maksim Alekseev

Если все df имеют одинаковую структуру:

def findYamlFiles(directory):
    main_df = None
    all_df = []
    for root, dirs, files in os.walk(directory):
        for file in files:
            if file.endswith('.yaml' or '.yml'):
                with open(os.path.join(root, file), 'r') as f:
                    yaml_data = yaml.safe_load(f)
                    all_df.append(pd.DataFrame(GetData(yaml_data, ''), columns=['Column 1', 'Column 2']))
  • 1 Вариант
print(pd.concat(all_df, ignore_index=True))

Вывод:

              Column 1 Column 2
0            /phone/os  android
1   /phone/size/length       80
2   /phone/size/height       10
3   /phone/size/weight       40
4         /phone/model    SE-35
5         /phone/price    20000
6            /phone/os  android
7   /phone/size/length       80
8   /phone/size/height       10
9   /phone/size/weight       40
10        /phone/model    SE-35
11        /phone/price    20000
  • 2 Вариант
print(pd.concat(all_df, axis=1))

Вывод:

             Column 1 Column 2            Column 1 Column 2
0           /phone/os  android           /phone/os  android
1  /phone/size/length       80  /phone/size/length       80
2  /phone/size/height       10  /phone/size/height       10
3  /phone/size/weight       40  /phone/size/weight       40
4        /phone/model    SE-35        /phone/model    SE-35
5        /phone/price    20000        /phone/price    20000
  • 3 Вариант
print(pd.merge(*all_df, on='Column 1'))

Вывод:

             Column 1 Column 2_x Column 2_y
0           /phone/os    android    android
1  /phone/size/length         80         80
2  /phone/size/height         10         10
3  /phone/size/weight         40         40
4        /phone/model      SE-35      SE-35
5        /phone/price      20000      20000
  • 4 Вариант (больше 2 файлов)
df = pd.DataFrame(GetData(yaml_data, ''), columns=['Column 1', file])
if main_df is None:
    main_df = df
    continue
main_df = pd.merge(main_df, df, on="Column 1")

Вывод:

             Column 1 car.yaml car2.yaml car3.yaml
0           /phone/os  android   android   android
1  /phone/size/length       80        80        80
2  /phone/size/height       10        10        10
3  /phone/size/weight       40        40        40
4        /phone/model    SE-35     SE-35     SE-35
5        /phone/price    20000     20000     20000

pd.merge

pd.concat

→ Ссылка
Автор решения: SVBazuev

Можно иначе структурировать данные:

import os
import yaml
import pandas as pd
from rich import print, inspect

# Укажите путь к директории с YAML-файлами
directory_path = r'C:\YAMLs'

# Словарь для хранения данных
data = {}

# Проходим по всем файлам в директории
for filename in os.listdir(directory_path):
    if filename.endswith('.yaml') or filename.endswith('.yml'):
        file_path = os.path.join(directory_path, filename)

        # Читаем YAML-файл
        with open(file_path, 'r', encoding='utf-8') as file:
            yaml_content = yaml.safe_load(file)
            phone_data = yaml_content['phone']
            # Добавляем данные в словарь
            data[filename] = {
                'os': phone_data['os'],
                'size.length': phone_data['size']['length'],
                'size.height': phone_data['size']['height'],
                'size.weight': phone_data['size']['weight'],
                'model': phone_data['model'],
                'price': phone_data['price']
            }

# Создаем DataFrame
df = pd.DataFrame(data).T

# Устанавливаем многоуровневый индекс для столбцов
df.columns = pd.MultiIndex.from_product([['phone'], df.columns])

# Печатаем результат
inspect(df, value=True)

output:

╭───────────────── <class 'pandas.core.frame.DataFrame'> ──────────────────╮
│ Two-dimensional, size-mutable, potentially heterogeneous tabular data.   │
│                                                                          │
│ ╭──────────────────────────────────────────────────────────────────────╮ │
│ │ │   │   │    phone                                                   │ │
│ │ │   │   │   │   os size.length size.height size.weight  model  price │ │
│ │ pho.yaml       ios          90          10          50  fE-35  70000 │ │
│ │ pho2.yml   android          80          12          40  SE-35  20000 │ │
│ │ pho3.yaml  windows         100          10          50  QE-95  37000 │ │
│ ╰──────────────────────────────────────────────────────────────────────╯ │
~                                                                          ~
╰──────────────────────────────────────────────────────────────────────────╯
→ Ссылка
Автор решения: strawdog

в общем случае, наверное, можно просто применить json_normalize:

files = ["f1.yaml", "f2.yaml", "f3.yaml", "f4.yaml"]

def framify(filename: str):
    with open(filename, "r") as f:
        retval = pd.DataFrame(pd.json_normalize(yaml.safe_load(f))).T
        retval = retval.rename(columns={0:filename})
    return retval
    
res = pd.concat([framify(file) for file in files], axis=1)

res:

                    f1.yaml    f2.yaml    f3.yaml  f4.yaml
phone.os            android      apple      apple  android
phone.size.length        80        NaN        NaN       75
phone.size.height        10        NaN        NaN        8
phone.size.weight        40        NaN        NaN       36
phone.model           SE-35  Iphone 15  Iphone 15    SE-35
phone.price           20000     150000        NaN    20000
phone.color             NaN       gold      black      NaN
phone.exist             NaN        NaN     absent      NaN
phone.camera.front      NaN        NaN        NaN     4 mp
phone.camera.back       NaN        NaN        NaN    64 mp
phone.camera.zoom       NaN        NaN        NaN        3
→ Ссылка