데이터프레임을 JSON 형식으로 변경하기

오늘은 데이터프레임의 형태를 어떻게 JSON으로 잘 변환할 수 있는지 알아볼 것입니다. 

무슨 작업을 하고 싶으냐…

위와 같이 표를 만들었다고 가정할까요?

저는 이런 데이터프레임을 JSON 데이터로 관리하고자 합니다. 

				
					[
  {
    "Category": "주연",
    "items": [
      {"직업": "학생", "이름": "모모", "능력": "염력"},
      {"직업": "학생", "이름": "오카룡", "능력": "변신"}
    ]
  },
  {
    "Category": "조연",
    "items": [
      {"직업": "외계인", "이름": "바모라", "능력": "변신"},
      {"직업": "영매사", "이름": "세이코", "능력": "영력"}
    ]
  },
  {
    "Category": "보조",
    "items": [
      {"직업": "귀신", "이름": "터보할멈", "능력": "속력"}
    ]
  }
]

				
			

이런 식으로 대분류에 따른 item 들이 어떤 key 에 어떤 Value를 가지고 있는지 정리하려 해요. 

JSON으로 데이터를 관리하면 웹 언어로 읽기가 편하겠지요.

Pandas 로 읽을 수 있는 데이터프레임을 어떻게 JSON 형태로 변경 가능한지 코드를 짜봅시다. 

예시 코드 뜯어보기

				
					import pandas as pd

# CSV 데이터 읽기 (파일 경로 필요)
df = pd.DataFrame({
    "Category": ["주연", "주연", "조연", "조연", "보조"],
    "직업": ["학생", "학생", "외계인", "영매사", "귀신"],
    "이름": ["모모", "오카룡", "바모라", "세이코", "터보할멈"],
    "능력": ["염력", "변신", "변신", "영력", "속력"]
})

# JSON 형태로 변환
grouped_data = (
    df.groupby("Category")
    .apply(lambda group: group[["직업", "이름", "능력"]].to_dict(orient="records"))
    .reset_index()
    .rename(columns={0: "items"})
    .to_dict(orient="records")
)

# JSON 출력
import json
print(json.dumps(grouped_data, ensure_ascii=False, indent=2))

				
			

예시로 코드를 들자면 위와 같습니다.

위에서 예시로 들었던 표를 데이터프레임으로 치환하면 위 코드에서 df와 같겠지요?

이 df를 JSON 형태로 변환하는 과정에 있는 코드를 보겠습니다. 

				
					# JSON 형태로 변환
grouped_data = (
    df.groupby("Category")
    .apply(lambda group: group[["직업", "이름", "능력"]].to_dict(orient="records"))
    .reset_index()
    .rename(columns={0: "items"})
    .to_dict(orient="records")
)
				
			

자, 중요한 위 코드를 하나씩 뜯어서 볼까요?

				
					print(df.groupby("Category"))

				
			

위 코드를 보시면 df.groupby 라는 method를 사용하신 것을 알 수 있습니다.

이 method를 사용할 때 인자로 “Category” 를 주었지요?

“Category”를 기준으로 GroupBy 화가 이뤄진 겁니다.

그래서 곧장 이것을 출력하면 뭐라고 메시지가 발생합니다. 

				
					<pandas.core.groupby.generic.DataFrameGroupBy object at 0x...>

				
			

이런 메시지가 발생하죠.

pandas 의 groupby method를 이용하여 데이터프레임을 객체로 변환한 상태라는 뜻입니다.

하지만 GroupBy method만 적용된 상태이지, 아무런 작업이 진행되지 않았으니.

현재 상태에 대한 이야기면 표시하는 겁니다. 

				
					grouped = df.groupby("Category")
print(grouped.groups)

				
			

이번에는 위와 같은 코드를 통해서 볼까요?

바로 위에서 바로 출력을 하려고 했던 GroupBy Object 에 grouped 라는 이름을 붙여줍니다.

그리고 grouped.groups 를 통해서 해당 GroupBy Object 의 기본 정보를 확인 가능합니다. 

 

				
					{
    "주연": [0, 1],
    "조연": [2, 3],
    "보조": [4]
}

				
			

위와 같이 나타납니다.

우리는 “Category” 를 기준으로 GroupBy 화를 시켰지요.

그러니까 Category 내부에 있는 값들을 Key 로 사용하고, 해당 Key 값을 가지는 행들이 몇 번째 행인지를 정리합니다. 

그런데 여기서 궁금한 점… 꼭 하나의 값으로만 GroupBy가 가능할까요?

 

				
					grouped = df.groupby(["a", "b"])

				
			

위와 같은 데이터를 특정한 2개의 값을 이용하여 분류를 하고 싶을 수도 있잖아요.

이렇게 하면 어떤 형태로 GroupBy 객체가 생성될까요?

				
					{
    ('X', 1): [0],
    ('X', 2): [1],
    ('Y', 1): [2],
    ('Y', 2): [3],
    ('Z', 1): [4]
}

				
			

바로 이렇게 됩니다.

두 개의 값이 짝을 이루는 좌표 형태로 하여, 해당 좌표를 가지는 행이 몇 번째 행인지 저장합니다. 

기억해두면 나중에 유용하게 써먹을 수 있겠습니다. 

				
					    df.groupby("Category")
    .apply(lambda group: group[["직업", "이름", "능력"]].to_dict(orient="records"))
				
			

그럼 이제 GroupBy 이후 뒤에 이어서 실행한 method가 어떤 method인지 알아봅시다.

.apply 이네요. 이 함수는 GroupBy 로 나눠진 객체에 내가 만든 lambda 함수를 적용하는 역할을 합니다. 

				
					# 결과 저장용 딕셔너리
result = {}

# groupby로 그룹 순회
for key, group in df.groupby("Category"):
    # 각 그룹에 대해 custom_function 실행
    result[key] = group[["직업", "이름", "능력"]].to_dict(orient="records")

print(result)

				
			

코드의 최적화를 위해 사용한 방식입니다.

편하게 이해하시려면 위랑 같은 방식을 수행한다고 보시면 될 것 같습니다. 

근데 이렇게 짜는 것 보다는 lambda 쓰는 거가 그냥 더 효율적이죠?

				
					 .to_dict(orient="records")
				
			

그럼 이제 이 방법에 대해서 알아볼까요?

records 방법은 특정한 행을 딕셔너리로 바꾸는 역할을 합니다. 

이때 열의 이름이 Key, 행의 값이 Value 가 되는 형태가 됩니다

				
					{
    "주연": [{'직업': '학생', '이름': '모모', '능력': '염력'}, {'직업': '학생', '이름': '오카룡', '능력': '변신'}],
    "조연": [{'직업': '외계인', '이름': '바모라', '능력': '변신'}, {'직업': '영매사', '이름': '세이코', '능력': '영력'}],
    "보조": [{'직업': '귀신', '이름': '터보할멈', '능력': '속력'}]
}

				
			

결과적으로 거대한 Object 로 뷴류가 되는 것입니다. 

lambda 함수를 거친 groupBy Object 는 위와 같은 형태를 가지고 있는 것이죠. 

				
					    df.groupby("Category")
    .apply(lambda group: group[["직업", "이름", "능력"]].to_dict(orient="records"))
    .reset_index()
				
			

그럼 이제 lambda 함수가 적용된 Object 를 가지고 .reset_index() 함수를 적용합니다.

이 작업을 통해서 Object 의 index 는 우리가 분류를 위해 사용했던 “Category” 가 Index의 역할을 하게 됩니다. 

만약 reset_index() 를 수행하지 않았다?

				
					Category
보조    [{'직업': '귀신', '이름': '터보할멈', '능력': '속력'}]
조연    [{'직업': '외계인', '이름': '바모라', '능력': '변신'}, {'직업': '영매사', '이름': '세이코', '능력': '영력'}]
주연    [{'직업': '학생', '이름': '모모', '능력': '염력'}, {'직업': '학생', '이름': '오카룡', '능력': '변신'}]
dtype: object

				
			

위와 같은 느낌이 된다는 것이죠.

Category 라는 열이 Index에 있는 겁니다.

저는 Category를 Index로 활용하고 싶지는 않습니다.

그럼 reset_index()를 적용하면 어떻게 될까요?

				
					0    [{'직업': '귀신', '이름': '터보할멈', '능력': '속력'}]
1    [{'직업': '외계인', '이름': '바모라', '능력': '변신'}, {'직업': '영매사', '이름': '세이코', '능력': '영력'}]
2    [{'직업': '학생', '이름': '모모', '능력': '염력'}, {'직업': '학생', '이름': '오카룡', '능력': '변신'}]
dtype: object

				
			

위와 같이 나타납니다.

각각의 index 가 생긴 것을 볼 수 있습니다. 

이걸 한 번 테이블로 나타내보겠습니다.

가장 앞쪽 열은 Index 가 되었고, Category, items 에 대한 값이 들어가네요.

사실 원래 items 라는 열은 없었습니다

				
					    .rename(columns={0: "items"})
    .to_dict(orient="records")
)
				
			

위와 같이 원래는 0이라는 이름으로 명명이 된 items 부분을  .rename() 기법으로 변경한 거랍니다.

이후에는 to_dict 기법을 이용해서 바뀐 열의 이름과 값을 매칭하면 우리가 원하는 Object 가 완성이 됩니다. 

				
					# JSON 출력
import json
print(json.dumps(grouped_data, ensure_ascii=False, indent=2))
				
			

이제 위와 같은 데이터를 통해 제작했던 데이터 구조를 json 형태로 변경 가능합니다.

ensure_ascii 를 False로 설정하는 이유는, True 로 하면 전부 아스키 문자로 나타나기 때문입니다.

				
					{"이름": "\uace0\uacf5"}

				
			

True 로 하면 위처럼 우리가 읽을 수 없는 언어로 저장이 되니까 조심합시다.

indent = 2로 넣는 이유는 우리가 읽기 쉽도록 2칸 들여쓰기 한다는 뜻입니다. 

마치며...

				
					[
    {
        "Category": "주연",
        "items": [
            {"직업": "학생", "이름": "모모", "능력": "염력"},
            {"직업": "학생", "이름": "오카룡", "능력": "변신"}
        ]
    },
    {
        "Category": "조연",
        "items": [
            {"직업": "외계인", "이름": "바모라", "능력": "변신"},
            {"직업": "영매사", "이름": "세이코", "능력": "영력"}
        ]
    },
    {
        "Category": "보조",
        "items": [
            {"직업": "귀신", "이름": "터보할멈", "능력": "속력"}
        ]
    }
]

				
			

결과적으로 이렇게 본래 만들고 싶었던 Json 데이터를 만들 수 있었습니다. 

				
					grouped = df.groupby(["a", "b"])