Парсинг различных yaml-файлов для сведения в одну таблицу

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

файл 1

name: phone1
spec:
  type1:
    - name: cpu
      value: 8 core
    - name: RAM
      value: 8Gb
      type:
        - name: ttt1
        - name: ttt2
  color: gold

файл 2

name: phone2
spec:
  type1:
    - name: cpu
      value: 4 core
    - name: RAM
      value: 4Gb
      type:
        - name: ttt3
        - name: ttt4
  size:
    length: 80
    height: 10
    weight: 40

файл 3

name: phone3
spec:
  type1:
    - name: cpu
      value: 2 core
    - name: RAM
      value: 2Gb
      type:
        - name: ttt5
        - name: ttt6

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

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

Phone file1 file2 file3
name phone1 phone2 phone3
spec.type1.0.name cpu cpu cpu
spec.type1.0.value 8 core 4 core 2 core
spec.type1.1.name RAM RAM RAM
spec.type1.1.value 8Gb 4Gb 2Gb
spec.type1.1.type.0.name ttt1 ttt3 ttt5
spec.type1.1.type.1.name ttt2 ttt4 ttt6
spec.size.length 80
spec.size.height 10
spec.size.weight 40
spec.color gold

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

пытаюсь их все распарсить: вот пример для одного файла:

import yaml
import pandas as pd

# Загрузка YAML-файла в словарь
def GetData(data, prefix):
    if isinstance(data, dict):
        for k, v in data.items():
            yield from GetData(v, f'{prefix}/{k}')
    elif isinstance(data, list):
        for i, v in enumerate(data):
            yield from GetData(v, f'{prefix}/{i}')
    else:
        yield (prefix, data)

# Путь к YAML-файлу
def var1(filename: str):
    with open(filename, "r") as f:
        yaml_data = yaml.safe_load(f)
        retval = pd.DataFrame(GetData(yaml_data, ''), columns=['0', filename])
    return retval

var1('file1.yaml').to_csv('data.csv', index=False)

Результат:

Phone file1
name phone1
spec.type1.0.name cpu
spec.type1.0.value 8 core
spec.type1.1.name RAM
spec.type1.1.value 8Gb
spec.type1.1.type.0.name ttt1
spec.type1.1.type.1.name ttt2
spec.color gold

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

А вот вариант для нескольких файлов:

import yaml
import pandas as pd
files = ["file1.yaml", "file2.yaml", "file3.yaml"]

def var2(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
pd.concat([var2(file) for file in files], axis=1).to_csv('results/data2.csv', index=True)

Результат:

Phone file1 file2 file3
name phone1 phone2 phone3
spec.type1 "[{'name': 'cpu', 'value': '8 core'}, {'name': 'RAM', 'value': '8Gb', 'type': [{'name': 'ttt1'}, {'name': 'ttt2'}]}]" "[{'name': 'cpu', 'value': '4 core'}, {'name': 'RAM', 'value': '4Gb', 'type': [{'name': 'ttt3'}, {'name': 'ttt4'}]}]" "[{'name': 'cpu', 'value': '2 core'}, {'name': 'RAM', 'value': '2Gb', 'type': [{'name': 'ttt5'}, {'name': 'ttt6'}]}]"
spec.color gold
spec.size.length 80
spec.size.height 10
spec.size.weight 40

Здесь другая проблема, не распарсиваются вложенные ключи.

Помогите с решением: что бы парсились все файлы, каждый вложенный ключ заводился в отдельную строку и пустые ключи тоже отображались в таблице


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

Автор решения: strawdog

Для структуры с вложенными списками json_normalize для вашего случая не подойдёт. Просто совместите свою функцию GetData c pd.concat, предварительно изменив индекс датафреймов:

def GetData(data, prefix):
    if isinstance(data, dict):
        for k, v in data.items():
            yield from GetData(v, f'{prefix}/{k}')
    elif isinstance(data, list):
        for i, v in enumerate(data):
            yield from GetData(v, f'{prefix}/{i}')
    else:
        yield (prefix, data)

def var2(file: str):    
    with open(file, "r") as f:
        yaml_data = yaml.safe_load(f)
        retval = pd.DataFrame(GetData(yaml_data, '')).set_index(0)
        retval = retval.rename(columns={1:file})
    return(retval)

files = ["file1.yaml", "file2.yaml", "file3.yaml"]

res = pd.concat([var2(file) for file in files], axis=1)

res:

                              file1.yaml     file2.yaml     file3.yaml
0                                                                     
/name                             phone1         phone2         phone3
/spec/type1/0/name                   cpu            cpu            cpu
/spec/type1/0/value               8 core         4 core         2 core
/spec/type1/1/name                   RAM            RAM            RAM
/spec/type1/1/value                  8Gb            4Gb            2Gb
/spec/type1/1/type/0/name           ttt1           ttt3           ttt5
/spec/type1/1/type/1/name           ttt2           ttt4           ttt6
/spec/color                         gold            NaN            NaN
/spec/size/length                    NaN             80            NaN
/spec/size/height                    NaN             10            NaN
/spec/size/weight                    NaN             40            NaN
→ Ссылка