오늘은 데이터프레임의 형태를 어떻게 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 의 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"])