본문 바로가기

JSON 형식을 dictionary로 변환할때 알아야 할 이론과 실습! (w/넷플릭스 시청데이터)

Derrick 발행일 : 2023-12-04
728x90
반응형

 

JSON 데이터 형식을 배우고 파이썬의 딕셔너리, 집합 등의 데이터 구조로 변환하여 주어진 데이터를 분석하는 법에 대해 학습합니다. 이후 실제로 넷플릭스 데이터 경진대회에서 사용되었던 JSON 데이터의 일부분을 활용하여 사람들에게 작품을 추천해주거나 A작품과 B작품이 얼마나 비슷한지 등을 분석할 수 있는 실습을 진행해봅니다!

# JSON : JavaScript Object Notation 
→ 데이터를 효과적으로 교환하기 위해 사용되는 경량의 데이터 형식
→ 프로그래밍 언어와 플랫폼에 독립적이기 때문에 대부분의 언어에서 지원. 특히 웹개발에 유용

 

JSON 형식으로 python 딕셔너리로 변환해서 분석하기!
넷플릭스 시청 데이터 분석하기 위한 기초 이론/실습 진행!

 

 

 

1. 딕셔너리(Dictionary)

딕셔너리는 파이썬에서도 사전의 의미와 역할로 수행하고, 내가 원하는 정보를 빠르게 찾아내는데 유용하다. 파이썬에서도 어떤 값(key)을 입력받으면 상응하는 값(value)을 출력받을 수 있다.
중괄호 '{ }' 를 사용해서 사용할 수 있다.

1) 딕셔너리 복습

Dictionary를 생성하고 값 추가 및 변경하는 것을 간단한 코드로 복습하자

 

# 딕셔너리 생성
empty_dict = {}
new_dict = {
    'apple': '사과',
    'book' : '책',
    'human': '사람',
}

 

# 딕셔너리 값 추가/변경
dictionary = {}
dictionary['apple'] = '사과'
dictionary['book'] = '책'
dictionary['human'] = '사람'

print(dictionary['book'])						# '책'
딕셔너리(Dictionary)는 대괄호( '[ ]' ) 안에 Key와 Value를 넣어줄 수 있다.

2) 딕셔너리 vs 리스트

자료구조 중 리스트나 튜플보다 딕셔너리를 써야 하는 이유는?
어떤 장점이 있고, 어떤 상황에서 딕셔너리를 써야 할까?
원하는 데이터를 빠르게 찾아내기 위해

 

# 1) 딕셔너리 = {id: 이름}
accounts = {
    "kdhong" : "Kildong Hong",
    " ~ ",
    ...
}

print(accounts["kdhong"])						# 'Kildong Hong'
위에서 Key는 'id', Value는 '이름'으로 볼 수 있다. 
딕셔너리에 id(key)를 넣어주면 해당하는 이름(value)이 잘 출력되는 것을 확인할 수 있다.

 

# 2) 리스트 = [(id, 이름)]
accounts = [
    ("kdhong", "Kildong Hong"),
    ....
]

# 이름 찾기
for id_, name in accounts:
    if id_ == "kdhong":
        print(name)
리스트나 튜플로 Key와 Value로 이루어진 자료를 만들고 찾고자 한다면, 위와 같이 for문을 통해 리스트 내 원소들을 하나씩 매칭하며 찾아가야 한다. 
→ 모든 아이디를 확인해야 하기 떄문에, 데이터가 많을수록 성능 차이가 나게 된다.

 # 실습 - 데이터 빠르게 탐색하기

주어진 텍스트 파일(netflix.txt)을 읽어와서 사용자 번호를 Key로 작품번호를 값(Value)으로 갖는 딕셔너리를 생성하는 함수를 생성하시오. ( =make_dictionary() )

 

[ netflix.txt ]

# 텍스트를 불러와서 딕셔너리 변환하는 함수
source_file = "netflix.txt"

def make_dictionary(filename):
    # 빈 딕셔너리 생성
    user_to_titles = {}
    
    # 텍스트 불러와서 딕셔너리에 추가
    with open(filename) as file:
        for line in file:
            # 자료가 ':'을 기준으로 분리
            user, title = line.strip().split(':')
            user_to_titles[user] = title
        
        return user_to_titles

# 함수 테스트
print(make_dictionary(source_file))

 

[ make_dictionary() 실행 결과 ]

 

2. 딕셔너리의 키(Key)

1) 특성

딕셔너리에서 찾고자 하는 Value는 어떤 값이든 들어갈 수 있지만, Key로 넣어주는 값에는 제한이 있다.  즉, 변할 수 없는 값들만 Key가 될 수 있다. 따라서 리스트(List)가 아닌 튜플(Tuple)로 Key로 넣어줄 수 있다.

 

# {[ID, 비밀번호] : 계정 정보}
kdhong = ["kdhong", "abcdefg"]

# 딕셔너리에 추가
account = {
    kdhong: ('Kildong Hong', ...),
}

# ID 변경
kdhong[0] = "kd.hong"							# Error
위의 코드를 보면 '[ ID, 비밀번호 ]' 로 구성된 리스트(kdhong)을 account 딕셔너리의 Key값으로 넣어주고, 계정정보가 Value로 들어가 있다. 여기서 kdhong의 ID를 변경한다면, 에러가 발생하게 된다.

→ 딕셔너리의 Key값에 2개 이상의 데이터가 들어간 상황이라면, 해당 Key값을 변경 불
Key 값을 바꾸고 싶다면, 해당 Key와 Value를 통째로 삭제 후 새로 추가 해야한다.

2) 딕셔너리의 Key 확인하기

딕셔너리 내 Key값을 확인하거나 삭제/재등록 하기 위해서는 해당 딕셔너리의 Key값 존재 여부를 확인할 수 있어야 한다. → ' in 연산자 ' 사용

 

# {id: 이름}
accounts = {
    "kdhong": "Kildong Hong",
}

print("kdhong" in accounts)					# True
print("minsu" in accounts)					# False

3) 딕셔너리 순회 - .items()

딕셔너리의 Key와 Value를 쌍으로 불러오는 걸 배워보자. → .items() 메서드 사용
경우에 따라서 딕셔너리에 저장된 데이터들을 하나씩 불러오는 작업이 필요할 때가 있다. 

 

# 딕셔너리 순회
accounts = {
    "kdhong": "Kildong Hong",
}

for username, name in accounts.items():
    print(username + " - " + name)				# kdhong - Kildong Hong
딕셔너리에 items() 메서드를 씌우면 튜플의 리스트 형식으로 생성된다.
ex) accounts.items() → [(kdhong, "Kildong Hong")]

→ Key와 Value 밖에 없기 때문에 Tuple의 크기는 항상 '2'
생성된 리스트(List)의 길이는 딕셔너리에 저장된 데이터의 수가 된다.

 # 실습 - 데이터 순회하기 ( .items() )

for문과 items() 메서드를 이용하면 딕셔너리의 모든 키와 값을 (Key, Value)의 형태로 리스트에 담을 수 있다. 아래 예제를 통해서 딕셔너리로 변환한 데이터의 통계를 내기 위해서 키(Key)와 값(Value)을 불러오고, 원하는 형식으로 변환하는 함수를 생성해보자.
- {사용자 : [작품리스트]} → {사용자 : 본 작품의 수}
-
user_to_titles : 사용자가 시청한 작품의 리스트, '{사용자 :[ 작품리스트]}' 형태

 

# 사용자가 시청한 작품의 리스트
user_to_titles = {
    1: [271, 318, 491],
    2: [318, 19, 2980, 475],
    3: [475],
    4: [271, 318, 491, 2980, 19, 318, 475],
    5: [882, 91, 2980, 557, 35],
}

# 작품의 리스트를 작품의 수로 변환하는 함수
def get_user_to_num_titles(user_to_titles):
    user_to_num_titles = {}
    
    # for문을 통해 딕셔너리 순회(Key, Value)
    for user, title in user_to_titles.items():
        user_to_num_titles[user] = len(title)
    
    return user_to_num_titles

# 함수 실행
print(get_user_to_num_titles(user_to_titles))

 

[ 실행 결과 ]

위의 결과를 보면 user(1~5)의 시청한 작품의 수가 각각 3, 4, 1, 7, 5로 잘 출력되는 것을 확인할 수 있다.

- for user, title in user_to_titles.items(): 

 : items() 메서드를 통해 'user_to_titles' 딕셔너리 내 Key와 Value가 Tuple 형태로 생성되기 때문에 자동으로 Key는 'user', Value는 'title'로 지정된다. 

 

3. JSON (JavaScript Object Notation)

'JSON' 이라는 특정 데이터 형식을 의미한다. ex) .txt, .doc, .json 등
그리고 데이터 형식은 '{key : value}' 을 가진다. 

# JSON의 특징
 - 웹 환경에서 데이터를 주고 받는 가장 표준적인 방식
  → 웹과 외부 서버의 소통은 JavaScript 코드를 이루어지는 경우가 많으므로 JSON으로 사용
 - Key를 이용하여 원하는 데이터만 빠르게 추출이 가능하다
 - 데이터가 쉽게 오염되지 않는다.
 - 다른 포맷에 비해 용량이 다소 큰 편

1) JSON과 Dictionary 변환

JSON과 Dictionary의 형태는 매우 유사하므로 서로 변환하는 것이 어렵지 않게 가능하다.
파이썬의 'json' 패키지에 포함된 함수를 이용해서 두 형식을 손쉽게 변환할 수 있다.

 # 변환할 때 사용하는 함수 
 - loads() : JSON 형태의 문자열을 딕셔너리로 변환 (모든 원소가 문자열 타입 설정)
  > JSON → Dictionary
 - dumps() : 딕셔너리를 JSON 형태의 문자열로 변환
  > Dictionary → JSON

 

[ JSON ↔ Dictionary 간 변환 ]

 # 실습 - JSON 데이터 다루기

주어진 JSON 파일들을 read()와 write() 메서드를 이용해서 딕셔너리로 변환하여 return하는 'create_dict()' 함수와 그 변환된 내용을 파일에 저장하는 'create_json()' 함수를 생성해보자
→ JSON 파일 : netflix.json
→ create_dict(filename) : json 파일(filename)을 입력받고 딕셔너리로 변환
→ create_json(dictionary, filename) 
  : 입력 받은 딕셔너리(dictonary)를 json 문자열로 변환해서 새로운 파일(filename)에 저장

 

# netflix.json
{
    "Iron Man": 31928,
    "Iron Man 2": 15293,
    "Dark Knight": 42107,
    "Man in Black": 20113
}

 

# json 패키지 import
import json

# JSON 파일을 열고 문자열을 딕셔너리로 변환
def create_dict(filename):
    with open(filename) as file:
    
        # json 파일의 모든 내용 불러오기
        json_string = file.read()
        
        # json 문자열 → 딕셔너리
        new_dict = json.loads(json_string)
        
        return new_dict
        # 'return json.loads(json_string)' 으로 한번에 작성 가능

# JSON 파일을 열고 딕셔너리를 JSON 형태의 문자열로 변환
def create_json(dictionary, filename):

    # 파일 작성모드(write)
    with open(filename, 'w') as file:
    
        # 딕셔너리 → json 문자열
        json_string = json.dumps(dictionary)
        
        # file에 string 데이터(변환한 내용)을 입력
        file.write(json_string)
        
        pass

# 함수 테스트
src = 'newflix.json'
dst = 'new_netflix.json'

netflix_dict = create_dict(src)
print("원래 데이터: " + str(netflix_dict))

# value 수정해서 반영하기
netflix_dict['Dark Knight'] = 39217
create_json(netflix_dict, dst)

updated_dict = create_dict(dst)
print("수정된 데이터: " + str(updated_dict))

 

[ 함수 실행 결과 ]

결과를 보면 주어진 json 파일(netflix.json)을 dictionary로 잘 변환되었으며, 특정 Key의 value값을 수정하여 json 형식으로 새로운 파일에 입력하는 것까지 정상 출력되었다.
 : 'Dark Knight' : 42107 → 'Dark Knight' : 39217

- json_string = file.read()
 : json 파일의 모든 내용을 다 불러오기
 → json 파일은 line마다 데이터를 읽어오는 것이 아니라 전체 데이터형식이 주어져 있다. 

 

4. 집합 (Set)

딕셔너리와는 다른 새로운 데이터 구조인 집합(set)에 대해 알아보자. 
집합의 가장 큰 2가지 특징은, "중복이 없다. 순서가 없다." 즉, 여러 원소들이 모인 집합의 경우 원소 간의 순서가 없고 이미 존재하는 원소를 새롭게 추가해도 같은 값으로 유지된다. 아래 코드를 확인해보자.
→ 순서와 중복이 없는 데이터 구조로, 데이터 분석에서 중복을 무시해야 하는 경우에 사용!

1) 집합의 기초 다루기

# 집합 선언하기 

# 집합 선언 (아래 모두 동일한 집합)
set1 = {1, 2, 3}
set2 = set([1, 2, 3])
set3 = {3, 2, 3, 1}

 

# 집합의 원소 추가/삭제 

num_set = {1, 3, 5, 7}

# 원소 추가
num_set.add(9)
num_set.update([3, 15, 4])

# 원소 제거
num_set.remove(7)
num_set.discard(13)
위의 4가지 함수(add, update, remove, discard)는 모두 각 기능을 수행 후 변경된 값으로 저장된다.

# 원소 추가
- add() : 특정 집합에 하나의 원소를 추가하는 메서드
- update() : 여러 원소들을 한번에 추가할 수 있음. 리스트, 집합 등의 형식으로 추가 가능

# 원소 제거
- remove() : 집합에 존재하는 원소 중 하나를 제거. (그 원소가 존재해야한다)
- discard() : '만약' 제거하고자 하는 원소가 집합 내 존재한다면, 제거하는 역할

 

# 집합 다루기

num_set = {1, 3, 5, 7}

print(6 in num_set)						# False
print(len(num_set))						# 4
- in 연산자를 집합에서도 사용할 수 있다.
- 집합의 크기를 알기 위해서 len() 함수를 통해 길이를 알 수 있다. (리스트와 동일)

 # 실습 - 데이터의 집합 나타내기

다음 게시물에서 다룰 넷플릭스 작품 A와 B를 모두 시청한 사람의 수, 둘 중 하나만 시청한 사람의 수를 이용하면 두 작품의 유사도를 유추할 수 있다. 이때 리스트와 딕셔너리 대신 집합을 사용하면 이를 더욱 쉽게 구할 수 있다.
집합중복과 순서를 무시하는 경우에 손쉽게 사용 가능!

아래의 미션을 모두 수행하면서 원소를 추가 및 삭제하는 함수를 연습해보자
 1) 정수 3과 5를 갖는 새로운 집합 생성(my_set)
 2) 정수 7을 집합에 추가
 3) 주어진 리스트(new_numbers)의 원소를 집합에 추가 
 4) 집합에 짝수를 모두 제거

 

# 1) Set 생성
my_set = {3, 5}

# 2) 정수 추가
my_set.add(7)

# 3) 리스트 추가
new_numbers = [1, 2, 3, 4, 5]
my_set.update(new_numbers)

# 4) 집합 내 짝수 제거
my_set = {num for num in my_set if (num % 2 != 0)}

2) 집합 연산

집합의 연산이란, 서로 다른 집합이 존재할 때, 그 집합 간의 관계를 나타내고 그를 통해 새로운 집합을 만들어내는 것을 말한다. 집합 연산자를 이용하면 합집합, 교집합, 차집합 등의 집합 연산을 수행할 수 있다.

 

[ 합집합 / 교집합 / 차집합 / XOR ]

- 교집합 
 : 집합 A와 B에 대해 모두 속하는 집합. (and 조건)
- 합집합 
 : 집합 A 혹은 B에 대해 하나라도 속해있으면 합집합에도 속한다. (or 조건)
- 차집합
 : 집합 A에는 속하지만, B에는 속하지 않는 집합. (A-B 호은 B-A)
- XOR 연산 (Exclusive Or)
 : 교집합을 제외한 집합 A or B (집합 A 혹은 B, 둘 중 하나만 만족)

 

# 집합 연산을 파이썬 연산자로 표현

# 파이썬 연산자를 통한 집합 연산
set1 = {1, 3, 5, 7}
set2 = {1, 3, 9, 27}

# 1) 합집합
union = set1 | set2

# 2) 교집합
intersection = set1 & set2

# 3) 차집합
diff = set1 - set2

# 4) XOR 연산
xor = set1 ^ set2

 # 실습 - 집합 연산자

집합 연산을 이용하면 넷플릭스 내 여러 작품의 시청자 통계를 쉽게 낼 수 있다.
각 영화별 시청자 리스트 정보가 있는 파일(viewrs.py)을 불러와서 '다크나이트(dark_knight)'와 '아이언맨(iron_man)', 두 작품의 시청자에 관한 4가지 종류의 통계를 산출해보자.

1) 두 작품을 모두 시청한 사람의 수
2) 두 작품 중 최소 하나를 시청한 사람의 수
3) 다크나이트만 시청한 사람의 수
4) 아이언맨만 시청한 사람의 수

 

[ viewers.py ]

 

# 각 영화별 시청자 리스트 import
from viewers import dark_knight, iron_man

# 불러온 데이터는 리스트 형식이므로 Set 구조로 변환
dark_knight_set = set(dark_knight)
iron_man_set = set(iron_man)

# 1) 두 작품을 모두 시청한 수 (교집합)
both = len(dark_knight_set & iron_man_set)

# 2) 두 작품 중 최소 하나를 시청한 수 (합집합)
either = len(dark_knight_set | iron_man_set)

# 3) 다크나이트만 시청한 수 (차집합)
dark_knight_only = len(dark_knight_set - iron_man_set)

# 4) 아이언만맨 시청한 수 (차집합)
iron_man_only = len(iron_man_set - dark_knight_set)

# 결과 출력
print("두 작품 모두 시청: {}명".format(both))
print("하나 이상 시청: {}명".format(either))
print("다크나이트만 시청: {}명".format(dark_knight_only))
print("아이언맨만 시청: {}명".format(iron_man_only))

 

[ 실행 결과 ]

 

3. 그래프 설정하기 (Matplotlib)

파이썬의 차트 라이브러리인 'matplotlib'에 대해 조금 더 설정하는 것을 연습해보자. matplotlib 라이브러리는 차트를 그리는 것뿐 아니라 차트에 더 많은 정보를 추가하고 보기 좋게 만드는 다양한 기능을 제공하고 있다. 아래 실습을 통해서 4가지 옵션을 추가해서 그래프를 설정해보자

- 한국어 표시를 위해 폰트 설정
- 차트의 제목 설정
- X축과 Y축에 라벨 표시
- 차트의 여백 조정

 

import matplotlib.pyplot as plt
import matplotlib.font_manager as fm

# 날짜별 온도 데이터를 세팅
dates = ["1월 {}일".format(day) for day in range(1, 32)]
temperatures = list(range(1, 32))

# 막대 그래프의 막대 위치를 결정하는 pos 선언
pos = range(len(dates))

# 한국어를 보기 좋게 표시할 수 있도록 폰트 설정
font = fm.FontProperties(fname='./NanumBarunGothic.ttf')

# 막대의 높이가 빈도의 값이 되도록 설정
plt.bar(pos, temperatures, align='center')

# 각 막대에 해당되는 단어를 입력
plt.xticks(pos, dates, rotation='vertical', fontproperties=font)

# 그래프의 제목 설정
plt.title('1월 중 기온 변화', fontproperties=font)

# Y축에 설명 추가
plt.ylabel('온도', fontproperties=font)

# 단어가 잘리지 않도록 여백 조정
plt.tight_layout()

# 그래프 저장
plt.savefig('graph.png')

 

[ 그래프 출력 실행 결과 ]

위의 코드는 이전에 실습했던 '가장 많이 사용되는 영어단어'에 사용했던 데이터를 참고해서 작성한 것이다. 출력된 차트를 보면서 코드를 수정해가며 공부해보자.

 

 

댓글