본문 바로가기
제로베이스 데이터 스쿨/일일 스터디 노트

27일차 스터디노트 / 파이썬 네이버 API, 인구 데이터 분석, 시각화

by 김뎀뎀 2023. 2. 8.

※제로베이스 데이터 취업스쿨 11기 수강 중

📗 27일차 공부 내용 요약

 

네이버 API의 상품 검색을 활용해 시각화해보았고, 인구 데이터를 활용해 소멸위험지역을 카르토그램으로 시각화해보았다.

 

1. 네이버 API : 네이버 API를 사용하는 법을 학습하고, 몰스킨 검색데이터를 불러와 시각화해보았다.

2. 인구분석 : 전국 인구 데이터와, 지도가 그려진 엑셀 데이터를 활용해 데이터를 정리하여 카르토그램과 지도에 시각화해보았다.

 


📖  27일차 공부 내용 자세히

 

1. 네이버 API

 

■ 네이버 검색 AP

  • 검색 API는 네이버 검색 결과를 뉴스, 백과사전, 블로그, 쇼핑, 영화, 웹 문서, 전문정보, 지식iN, 책, 카페글 등 분야별로 볼 수 있는 API이다
  • 검색 결과를 XML 형식 또는 JSON 형식으로 반환한다
  • API를 호출 할 때는 검색어와 검색 조건을 쿼리 스트링(Query String)형식의 데이터로 전달한다

 

■ 몰스킨 상품 검색 데이터 정리/시각화

진행순서
  1. gen_search_url (generate url)
  2. gen_result_onpage (get data on one page)
  3. get_fields (convert pandas data frame)
  4. actMain (all data gethering)
  5. toExcel (export to excel)

❶ gen_search_url() ⇒ url 생성

네이버 쇼핑 API 설명

def gen_search_url(api_node, search_text, start_num, disp_num):
    base = "https://openapi.naver.com/v1/search"
    node = "/" + api_node + ".json"
    param_query = "?query=" + urllib.parse.quote(search_text)
    param_start = "&start=" + str(start_num)
    param_disp = "&display=" + str(disp_num)
        
    return base + node + param_query + param_start + param_disp

 

❷  get_result_onpage() ⇒ 검색 결과 도출

import json
import datetime

def get_result_onpage(url):
    request = urllib.request.Request(url)
    request.add_header("X-Naver-Client-Id",client_id)
    request.add_header("X-Naver-Client-Secret",client_secret)
    response = urllib.request.urlopen(request)
    print("[%s] Url Request Success" % datetime.datetime.now())

    return json.loads(response.read().decode("utf-8"))

#jason은 딕셔너리 형태

 

❸ get_fields() ⇒ 데이터프레임 변환

import pandas as pd

def get_fields(json_data):
    
    title = [ each["title"] for each in json_data["items"] ]
    link = [ each["link"] for each in json_data["items"] ]
    lprice = [ each["lprice"] for each in json_data["items"] ]
    mall_name = [ each["mallName"] for each in json_data["items"] ]
    
    result_pd = pd.DataFrame({
        "title":title,
        "link":link,
        "lprice":lprice,
        "mall":mall_name,
    }, columns=["title", "lprice", "link", "mall"])
    
    return result_pd

 

❹ delete_tag() ⇒ 태그 삭제

def delete_tag(input_str):
    input_str = input_str.replace("<b>", "")
    input_str = input_str.replace("</b>", "")
    return input_str

def get_fields(json_data):
    
    title = [ delete_tag(each["title"]) for each in json_data["items"] ]
    link = [ each["link"] for each in json_data["items"] ]
    lprice = [ each["lprice"] for each in json_data["items"] ]
    mall_name = [ each["mallName"] for each in json_data["items"] ]
    
    result_pd = pd.DataFrame({
        "title":title,
        "link":link,
        "lprice":lprice,
        "mall":mall_name,
    }, columns=["title", "lprice", "link", "mall"])
    
    return result_pd

 

❺ actMain() ⇒ 모든 데이터 취합(1000개)

result_mol = []

for n in range(1,1000,100):
    url = gen_search_url("shop", "몰스킨", n, 100)
    json_result = get_result_onpage(url)
    pd_result = get_fields(json_result)
    
    result_mol.append(pd_result)

result_mol = pd.concat(result_mol)

#인덱스 리셋
result_mol.reset_index(drop=True, inplace = True) #drop=True 기존 인덱스 삭제

#가격 데이터타입 실수형으로 변환
result_mol["lprice"] = result_mol["lprice"].astype("float")

result_mol

 

❻ to_excel() ⇒ 엑셀로 저장

!pip install xlsxwriter

writer = pd.ExcelWriter("../data/06_molskin_diary_in_naver_shop.xlsx", engine="xlsxwriter")
result_mol.to_excel(writer, sheet_name="Sheet1")

workbook = writer.book
worksheet = writer.sheets["Sheet1"]
worksheet.set_column("A:A",4)
worksheet.set_column("B:B",60)
worksheet.set_column("C:C",10)
worksheet.set_column("F:F",10)

worksheet.conditional_format("C2:C1001", {"type":"3_color_scale"})
writer.save()

❼ 시각화

plt.figure(figsize=(15,6))
sns.countplot(
    data = result_mol,
    x= result_mol["mall"],
    palette="RdYlGn",
    order = result_mol["mall"].value_counts().index
)
plt.xticks(rotation=90)
plt.show()

 

 

2. 인구분석

 

1) 데이터 읽고 소멸 위험 지역 파악하기

인구 데이터 불러오고 Nan값 채우기

population = pd.read_excel("../data/07_population_raw_data.xlsx", header = 1)
population.fillna(method="pad", inplace = True)

 

컬럼 이름 변경, 값 변경

#컬럼명 변경
population.rename(
    columns={
        "행정구역(동읍면)별(1)":"광역시도",
        "행정구역(동읍면)별(2)":"시도",
        "계":"인구수"
    }, inplace = True
)

# 소계 제거
population = population[population["시도"] != "소계"]
population.head()

#컬럼명 변경
population.is_copy = False #wanrning을 내보내지 말아달라

population.rename(
    columns = {"항목":"구분"}, inplace=True
)

#값변경
population.loc[population["구분"]=="총인구수 (명)", "구분"] = "합계"
population.loc[population["구분"]=="남자인구수 (명)", "구분"] = "남자"
population.loc[population["구분"]=="여자인구수 (명)", "구분"] = "여자"

 

❸ 소멸 지역 조사하기 위한 데이터 정리

# 데이터 묶기
population["20~39세"] = (
    population["20 - 24세"] + population["25 - 29세"] + population["30 - 34세"] + population["35 - 39세"]
)

population["65세이상"] = (
    population["65 - 69세"] + population["70 - 74세"] + population["75 - 79세"] + population["80 - 84세"]
    + population["85 - 89세"] + population["90 - 94세"] + population["95 - 99세"] + population["100+"]
)

# 피봇테이블
pop = pd.pivot_table(
    data = population,
    index = ["광역시도", "시도"],
    columns = ["구분"],
    values = ["인구수", "20~39세", "65세이상"]
)

 

❹ 소멸 지역 계산해서 데이터 정리

#소멸비율
pop["소멸비율"] = pop["20~39세","여자"] / (pop["65세이상", "합계"] / 2)

#소멸위기지역
pop["소멸위기지역"] = pop["소멸비율"] < 1.0

#reset index
pop.reset_index(inplace = True)

#다중 컬럼 정리
tmp_columns = [
    pop.columns.get_level_values(0)[n] + pop.columns.get_level_values(1)[n]
    for n in range(0, len(pop.columns.get_level_values(0)))    
]

pop.columns = tmp_columns

 

2) 인구현황데이터에 지도 ID 만들기

❶ 일반 시 이름과 세종시, 광역시도 일반구 정리

#ID를 넣기 위한 리스트 생성
si_name = [None] * len(pop)

for idx, row in pop.iterrows():
    if row["광역시도"][-3:] not in ["광역시", "특별시", "자치시"]: #광역시도에서 마지막 3글자가 광역시,특별시,자치시가 아니라면
        si_name[idx] = row["시도"][:-1] #시도에서 마지막 1글자(군,구)를 빼고 저장해라
        
    elif row["광역시도"] == "세종특별자치시":
        si_name[idx] = "세종"
    
    else:
        if len(row["시도"]) == 2: #시도가 2글자면
            si_name[idx] = row["광역시도"][:2] + " " + row["시도"] #광역시도 앞 2글자와 시도를 더해서 저장해라
        else: #시도가 2글자가 아니면
            si_name[idx] = row["광역시도"][:2] + " " + row["시도"][:-1] #광역시도 앞 2글자와 시도 마지막 1글자를 빼고 더해서 저장해라

 

❷ 행정구 정리

tmp_gu_dic = {
    "수원" : ["장안구","권선구","팔달구","영통구"],
    "성남" : ["수정구","중원구","분당구"],
    "안양" : ["만안구","동안구"],
    "안산" : ["상록구","단원구"],
    "고양" : ["덕양구","일산동구","일산서구"],
    "용인" : ["처인구","기흥구","수지구"],
    "청주" : ["상당구","서원구","흥덕구","청원구"],
    "천안" : ["동남구","서북구"],
    "전주" : ["완산구","덕진구"],
    "포항" : ["남구","북구"],
    "창원" : ["의창구","성산구","진해구","마산합포구","마산회원구"],
    "부천" : ["오정구","원미구","소사구"]
}

for idx, row in pop.iterrows():
    if row["광역시도"][-3:] not in ["광역시","특별시","자치시"]:
        for keys, values in tmp_gu_dic.items():
            if row["시도"] in values:
                if len(row["시도"]) == 2:
                    si_name[idx] = keys + " " + row["시도"]
                
                elif row["시도"] in ["마산합포구", "마산회원구"]:
                    si_name[idx] = keys + " " + row["시도"][2:-1]
                    
                else:
                    si_name[idx] = keys + " " + row["시도"][:-1]

 

❸ 고성군 정리

for idx, row in pop.iterrows():
    if row["광역시도"][-3:] not in ["광역시", "특별시", "자치시"]:
        if row["시도"][:-1] == "고성" and row["광역시도"] == "강원도":
            si_name[idx] = "고성(강원)"
        elif row["시도"][:-1] == "고성" and row["광역시도"] == "경상남도":
            si_name[idx] = "고성(경남)"

 

❹ 인구현황 데이터프레임 정리 (pop)

pop["ID"] = si_name

del pop["20~39세남자"]
del pop["65세이상남자"]
del pop["65세이상여자"]

 

3) 카르토그램으로 인구현황 시각화

❶ 지도모양 그려진 엑셀 불러와서, 좌표로 활용한 데이터프레임 만들기(draw_korea)

draw_korea_raw = pd.read_excel("../data/07_draw_korea_raw.xlsx")

#stack()을 통해 컬럼을 인덱스로 보내서 데이터프레임 만들기
draw_korea_raw_stacked = pd.DataFrame(draw_korea_raw.stack())

#reset_index
draw_korea_raw_stacked.reset_index(inplace=True)

#컬럼명 바꾸기
draw_korea_raw_stacked.rename(
    columns={
        "level_0":"y",
        "level_1":"x",
        0:"ID"
    }, inplace = True
)

#draw_korea 변수에 저장
draw_korea = draw_korea_raw_stacked

❷ 경계선 작성

#(y,x)
BORDER_LINES = [
    [(5,1), (5,2), (7,2), (7,3), (11,3), (11,0)], #인천    
    [(5,4), (5,5), (2,5), (2,7), (4,7), (4,9), (7,9), (7,7), (9,7), (9,5), (10,5), (10,4), (5,4)], #서울
    [(1,7), (1,8), (3,8), (3,10), (10,10), (10,7), (12,7), (12,6), (11,6), (11,5),(12,5), (12,4), (11,4), (11,3)], #경기도
    [(8,10), (8,11), (6,11), (6,12)], #강원도
    [(12,5), (13,5), (13,4), (14,4), (14,5), (15,5), (15,4), (16,4), (16,2) ], #충청도
    [(16,4), (17,4), (17,5), (16,5), (16,6), (19,6), (19,5), (20,5), (20,4), (21,4), (21,4), (21,3), (19,3), (19,1)], #전라북도
    [(13,5), (13,6), (16,6)], #대전시
    [(13,5), (14,5)], #세종시
    [(21,2), (21,3), (22,3), (22,4), (24,4), (24,2), (21,2)], #광주
    [(20,5), (21,5), (21,6), (23,6)], #전라남도
    [(10,8), (12,8), (12,9), (14,9), (14,8), (16,8), (16,6)], #충청북도
    [(14,9), (14,11), (14,12), (13,12), (13,13)], #경상북도
    [(15,8), (17,8), (17,10), (16,10), (16,11), (14,11),], #대구
    [(17,9), (18,9), (18,8), (19,8), (19,9), (20,9), (20,10), (21,10)], #부산
    [(16,11), (16,13)],
    [(27,5), (27,6), (25,6)]    
]

 

❸ 인구현황 데이터와 좌표 데이터 합치기 (pop, draw_korea)

#차 집합 제거
tmp_list = set(pop["ID"].unique()) -set(draw_korea["ID"].unique())

for tmp in tmp_list:
    pop = pop.drop(pop[pop["ID"] == tmp].index)
    
pop = pd.merge(pop, draw_korea, how = "left", on = "ID")

 

❹ 그림을 그리기 위한 데이터를 계산하는 함수

def get_data_info(targetData, blockedMap):
    #경계선의 색상을 계산
    whitelabelmin = (
        max(blockedMap[targetData]) - min(blockedMap[targetData])
    ) * 0.25 + min(blockedMap[targetData])
    
    vmin = min(blockedMap[targetData])
    vmax = max(blockedMap[targetData])
    
    mapdata = blockedMap.pivot_table(index = "y", columns = "x", values = targetData)
    
    return mapdata, vmax, vmin, whitelabelmin
    
def get_data_info_for_zero_center(targetData, blockedMap):
    whitelabelmin = 5
    
    tmp_max = max(
        [np.abs(min(blockedMap[targetData])), np.abs(max(blockedMap[targetData]))]
    ) #np.abs 절댓값
    
    vmin, vmax = -tmp_max, tmp_max
    
    mapdata = blockedMap.pivot_table(index = "y", columns ="x", values=targetData)
    
    return mapdata, vmax, vmin, whitelabelmin
    
def plot_text(targetData, blockedMap, whitelabelmin):
    for idx,row in blockedMap.iterrows():
        # 엑셀의 셀안 지역 이름이 4글자 이상이면 2줄로 작성되는 점을 반영
        # dispname = 셀에 나타나는 이름
        if len(row["ID"].split()) == 2:
            dispname = "{}\n{}".format(row["ID"].split()[0], row["ID"].split()[1])
        elif row["ID"][:2] == "고성":
            dispname = "고성"
        else:
            dispname = row["ID"]
        
        #3글자 이상이면 글씨 작게, 아니면 조금 크게
        if len(dispname.splitlines()[-1]) >= 3:
            fontsize, linespacing = 9.5, 1.5
        else:
            fontsize, linespacing = 10.8, 1.2
        
        annocolor = "white" if np.abs(row[targetData]) > whitelabelmin else "black"
        
        #주석 기능(그래프 위에 글씨를 다는 거)
        plt.annotate(
            dispname,
            (row["x"] + 0.5, row["y"] + 0.5),
            weight = "bold",
            color = annocolor,
            fontsize = fontsize,
            linespacing = linespacing,#줄간격
            ha = "center", #수평 정렬
            va = "center", #수직 정렬
        )
        
        
def plot_text(targetData, blockedMap, whitelabelmin):
    for idx,row in blockedMap.iterrows():
        # 엑셀의 셀안 지역 이름이 4글자 이상이면 2줄로 작성되는 점을 반영
        # dispname = 셀에 나타나는 이름
        if len(row["ID"].split()) == 2:
            dispname = "{}\n{}".format(row["ID"].split()[0], row["ID"].split()[1])
        elif row["ID"][:2] == "고성":
            dispname = "고성"
        else:
            dispname = row["ID"]
        
        #3글자 이상이면 글씨 작게, 아니면 조금 크게
        if len(dispname.splitlines()[-1]) >= 3:
            fontsize, linespacing = 9.5, 1.5
        else:
            fontsize, linespacing = 10.8, 1.2
        
        annocolor = "white" if np.abs(row[targetData]) > whitelabelmin else "black"
        
        #주석 기능(그래프 위에 글씨를 다는 거)
        plt.annotate(
            dispname,
            (row["x"] + 0.5, row["y"] + 0.5),
            weight = "bold",
            color = annocolor,
            fontsize = fontsize,
            linespacing = linespacing,#줄간격
            ha = "center", #수평 정렬
            va = "center", #수직 정렬
        )

 

❺ 인구현황 시각화

 

< 소멸 위기 지역 >

pop["소멸위기지역"] = [1 if con else 0 for con in pop["소멸위기지역"]]
drawKorea("소멸위기지역", pop, "Reds")

 

mymap = folium.Map(location=[36.2002, 127.054], zoom_start=7)

mymap.choropleth(
    geo_data=geo_str,
    data=pop_folium["소멸위기지역"],
    key_on="feature.id",
    columns = [pop_folium.index, pop_folium["소멸위기지역"]],
    fill_color = "PuRd"
)

mymap


➰ 27일차 후기

이번 파트의 마지막 파트라서 그런지 기존에 비해서 어려웠다.

코드들이 길고 복잡했고, 데이터 정리 과정과, 함수들도 여러개 만들어 사용해서 한 번 본 것만으로는 완전히 습득되지는 않았서 몇번은 봐야할 것 같다.

요즘 피지컬100을 챙겨봐서 오늘도 새로운 화가 공개되는 날이라 시청했는데, 매 화를 볼 때마다 가지고 있는 힘과 체력도 중요하지만 집념도 그에 못지않게 필요하다는 걸 느낀다.

쉬이 포기하지 말고, 안주하지 말고 끈기있게 해보자!

 

 


※본 내용은 제로베이스 데이터 취업 스쿨에서 제공하는 학습 내용에 기반합니다.