

PythonでCSVをYAMLに変換したいのですが、想定した通りに出力されません。

#### CSVファイル

[code]

    version,3
    services,
    db,
    container_name,atcoder-stream-db
    image,postgres:12.1
    expose,"- ""5432"""
    
    web-backend,
    container_name,atcoder-stream-backend
    build,
    context,./atcoder-stream-backend
    dockerfile,Dockerfile
    volumes
    ,- ./atcoder-stream-backend/atcoder-stream-api
    ,- ./atcoder-stream-backend/libraries/lib/lib
    ,- ./atcoder-stream-backend/libraries/twitterapi/twitterapi
    ports," - ""8000:8000"""
    depends_on, - db
    command,"sh -c ""python /app/src/atcoder-stream-api/manage.py migrate && python"
    web-frontend,
    container_name,atcoder-stream-frontend
    build,
    context,./atcoder-stream-frontend
    dockerfil,Dockerfile
    volumes,
    ,- ./atcoder-stream-frontend:/app
    port," - ""3000:3000"""
    command," sh -c ""cd /app && yarn start"""
    
[/code]

#### 想定しているYAMLファイル

[code]

    version: "3"
    
    services:
      db:
        container_name: atcoder-stream-db
        image: postgres:12.1
        expose:
          - "5432"
    
      web-backend:
        container_name: atcoder-stream-backend
        build:
          context: ./atcoder-stream-backend
          dockerfile: Dockerfile
        volumes:
          - ./atcoder-stream-backend/atcoder-stream-api
          - ./atcoder-stream-backend/libraries/lib/lib
          - ./atcoder-stream-backend/libraries/twitterapi/twitterapi
        ports:
          # host:container
          - "8000:8000"
        depends_on:
          - db
        command: sh -c "python /app/src/atcoder-stream-api/manage.py migrate && python /app/src/atcoder-stream-api/manage.py runserver 0.0.0.0:8000"
    
      web-frontend:
        container_name: atcoder-stream-frontend
        build:
          context: ./atcoder-stream-frontend
          dockerfile: Dockerfile
        volumes:
          - ./atcoder-stream-frontend:/app
        ports:
          # host:container
          - "3000:3000"
        command: sh -c "cd /app && yarn start"
    
[/code]

Pythonでは以下のように記述しました。

[code]

    import pandas as pd
    from pathlib import Path
    import yaml
    
    path = Path('keywords.csv')
    df = pd.read_csv(path, encoding='cp932')
    # print(df)
    # path2 = Path('data2.csv')
    # df.to_csv(path2, encoding='cp932', index=False)
    # print(df)
    df = pd.read_csv(path, encoding='cp932',index_col=0, header=None, na_filter=False)
    with open('config.yml','w') as yaml_file:
        yaml.dump(
            df.to_dict(orient='dict'),
            yaml_file,
            sort_keys=False,
        )
    
[/code]

#### 現状の出力結果

[code]

    1:
      version: '3'
      services: ''
      db: ''
      container_name: atcoder-stream-frontend
      image: postgres:12.1
      expose: '- "5432"'
      web-backend: ''
      build: ''
      context: ./atcoder-stream-frontend
      dockerfile: Dockerfile
      volumes: ''
      ? ''
      : '- ./atcoder-stream-frontend:/app'
      ports: ' - "8000:8000"'
      depends_on: ' - db'
      command: ' sh -c "cd /app && yarn start"'
      web-frontend: ''
      dockerfil: Dockerfile
      port: ' - "3000:3000"'
    
[/code]

#### 問題点

  * 空白行に `''` があり、削除したい。
  * dbとweb-backend:にcontainer_nameがあるのですがweb-backendでは認識され表示されません。
  * value配下のデータが出力されません。

csvの記述に問題があるのでしょうか？

YAMLからCSVに変換する情報があるのですが、私のような方法の情報が少ない。


CC BY-SA 4.0
2022-04-04T23:41:48.703
88170
2022-04-06T17:40:00.890
2022-04-05T00:16:39.393
3060
43098
question
python csv yaml
PythonでCSVファイルをYAMLに変換したいが、上手く出力されない


だいたいの原因はcsvの構成が多くの項目を同一レベルでフラットに書いているためでしょう。

  * 空白行に `''` があり、削除したい。  
→おそらくCSVに空白行があるからでしょうね。CSVの空白行を削れば良いと思われます。

  * dbとweb-backend:にcontainer_nameがあるのですがweb-backendでは認識され表示されません。  
→それらの項目をフラットに書いているため同じ名前があると後からの値に置き換えられてしまうからですね。

  * value配下のデータが出力されません。  
→`value`ではなく`volumes`のtypoでしょうね。  
pandas
DataFrameの`to_dict()`でどのような変換が為されるか、目的の.ymlを得るためにはどのようなDataFrame/dictionaryの構成にすれば良いか、そのためにCSVはどうすれば良いかを知って対応する必要があるでしょう。

* * *

質問や上記の内容を考えると、単純なpandas DataFrameの`to_dict()`だけで目的の.ymlを得るのは難しいと思われます。

以下のようなサイトがあり、「想定しているYAMLファイル」を入れて変換させてみるとCSVは出来ませんでしたがJSONは出来ました。  
[Best Online YAML Converter](https://codebeautify.org/yaml-to-json-xml-csv/)  
上記ページの結果がこうなったので、これに相当するdictionaryが作れれば、.ymlを得ることが出来ると考えられます。

[code]

    {
        "version": "3",
        "services": {
            "db": {
                "container_name": "atcoder-stream-db",
                "image": "postgres:12.1",
                "expose": [
                    "5432"
                ]
            },
            "web-backend": {
                "container_name": "atcoder-stream-backend",
                "build": {
                    "context": "./atcoder-stream-backend",
                    "dockerfile": "Dockerfile"
                },
                "volumes": [
                    "./atcoder-stream-backend/atcoder-stream-api",
                    "./atcoder-stream-backend/libraries/lib/lib",
                    "./atcoder-stream-backend/libraries/twitterapi/twitterapi"
                ],
                "ports": [
                    "8000:8000"
                ],
                "depends_on": [
                    "db"
                ],
                "command": "sh -c \"python /app/src/atcoder-stream-api/manage.py migrate && python /app/src/atcoder-stream-api/manage.py runserver 0.0.0.0:8000\""
            },
            "web-frontend": {
                "container_name": "atcoder-stream-frontend",
                "build": {
                    "context": "./atcoder-stream-frontend",
                    "dockerfile": "Dockerfile"
                },
                "volumes": [
                    "./atcoder-stream-frontend:/app"
                ],
                "ports": [
                    "3000:3000"
                ],
                "command": "sh -c \"cd /app && yarn start\""
            }
        }
    }
    
[/code]

このようなネストしたり途中にリスト(配列)があるようなデータを作成するためにはCSVの方もそれを考慮した構成が必要でしょうし、一部だけをリスト(配列)化するためには単純な`to_dict()`だけででは難しそうで、それなりの処理が必要と思われます。

あと、PyYAMLモジュールではそのままだと文字列がシングルクォーテーションで表現されるので使い分けしたい場合は工夫が必要になります。  
多くの記事で`ruamel.yaml`を使う回答が簡単そうなので、それを応用します。  
[Python-yaml script and double
quotes](https://stackoverflow.com/q/67476386/9014308)  
[How to print a value with double quotes and spaces in
YAML?](https://stackoverflow.com/q/47542343/9014308)  
[Python YAML dumper single quote and double quote
issue](https://stackoverflow.com/q/53606252/9014308)  
[Python yaml generate few values in
quotes](https://stackoverflow.com/q/52423986/9014308)

* * *

作成する元データは、例えばExcelだとこんな感じの表が考えられます。  
[![ExcelImage](https://i.stack.imgur.com/sPNoX.jpg)](https://i.stack.imgur.com/sPNoX.jpg)

それと対応したCSVはこちらになります。

[code]

    version,"""3""",,,
    services,db,,,
    ,,container_name,atcoder-stream-db,
    ,,image,postgres:12.1,
    ,,expose,"- ""5432""",
    ,web-backend,container_name,atcoder-stream-backend,
    ,,build,context,./atcoder-stream-backend
    ,,,dockerfile,Dockerfile
    ,,volumes,- ./atcoder-stream-backend/atcoder-stream-api,
    ,,,- ./atcoder-stream-backend/libraries/lib/lib,
    ,,,- ./atcoder-stream-backend/libraries/twitterapi/twitterapi,
    ,,ports," - ""8000:8000""",
    ,,depends_on,- db,
    ,,command,"sh -c ""python /app/src/atcoder-stream-api/manage.py migrate && python /app/src/atcoder-stream-api/manage.py runserver 0.0.0.0:8000""",
    ,web-frontend,container_name,atcoder-stream-frontend,
    ,,build,context,./atcoder-stream-frontend
    ,,,dockerfil,Dockerfile
    ,,volumes,- ./atcoder-stream-frontend:/app,
    ,,port," - ""3000:3000""",
    ,,command," sh -c ""cd /app && yarn start""",
    
[/code]

* * *

Pythonスクリプトはこんな感じで、読み込んだCSVの空欄を埋めて、groupbyで切り出して、ネストされた構造的なdictionaryを組み立てていく形です。  
質問の.ymlに特化した逐次処理のプログラムですので、突っ込みどころは多々あるでしょうが、まずは出来ることを目指すとこんな感じになります。

[code]

    import pandas as pd
    from pathlib import Path
    import ruamel.yaml
    from ruamel.yaml.scalarstring import DoubleQuotedScalarString as dq
    yaml = ruamel.yaml.YAML()
    yaml.indent(sequence=4, offset=2)
    yaml.width = 1024
    
    path = Path('keywords.csv')
    df = pd.read_csv(path, encoding='cp932', header=None, skipinitialspace=True, dtype='str')
    for n in range(3):
      df[n].fillna(method='ffill',inplace=True)
    df.fillna('',inplace=True)
    
    def df_to_dict(frame, currentindex, yamldict):
        nextindex = currentindex + 1
        for key,workframe in frame.groupby(by=currentindex, sort=False):
            value = workframe.at[workframe.index.values[0],nextindex].strip()
            if len(workframe) == 1:
                if value:
                    uselist = False
                    if value.startswith('- '):
                        value = value[2:]
                        uselist = True
                    if value.startswith('"'):
                        value = dq(value.strip('"'))
                    if uselist:
                        value = [value]
                    yamldict[key] = value
            elif value.startswith('- '):
                values = [v.strip()[2:] for v in workframe[nextindex].tolist()]
                yamldict[key] = values
            else:
                work2frame = workframe.iloc[:,workframe.columns!=currentindex]
                workdict = {}
                df_to_dict(work2frame, nextindex, workdict)
                yamldict[key] = workdict
    
    yamldict = {}
    df_to_dict(df, 0, yamldict)
    
    with open('config.yml','w') as yaml_file:
        yaml.dump(
            yamldict,
            yaml_file,
        )
    
[/code]

結果のファイルは以下になります。

[code]

    version: "3"
    services:
      db:
        container_name: atcoder-stream-db
        image: postgres:12.1
        expose:
          - "5432"
      web-backend:
        container_name: atcoder-stream-backend
        build:
          context: ./atcoder-stream-backend
          dockerfile: Dockerfile
        volumes:
          - ./atcoder-stream-backend/atcoder-stream-api
          - ./atcoder-stream-backend/libraries/lib/lib
          - ./atcoder-stream-backend/libraries/twitterapi/twitterapi
        ports:
          - "8000:8000"
        depends_on:
          - db
        command: sh -c "python /app/src/atcoder-stream-api/manage.py migrate && python /app/src/atcoder-stream-api/manage.py runserver 0.0.0.0:8000"
      web-frontend:
        container_name: atcoder-stream-frontend
        build:
          context: ./atcoder-stream-frontend
          dockerfil: Dockerfile
        volumes:
          - ./atcoder-stream-frontend:/app
        port:
          - "3000:3000"
        command: sh -c "cd /app && yarn start"
    
[/code]


CC BY-SA 4.0
2022-04-06T17:40:00.890
88204
2022-04-06T17:40:00.890
26370
88170
answer