※제로베이스 데이터 취업스쿨 11기 수강 중
📗 27일차 공부 내용 요약
네이버 API의 상품 검색을 활용해 시각화해보았고, 인구 데이터를 활용해 소멸위험지역을 카르토그램으로 시각화해보았다.
1. 네이버 API : 네이버 API를 사용하는 법을 학습하고, 몰스킨 검색데이터를 불러와 시각화해보았다.
2. 인구분석 : 전국 인구 데이터와, 지도가 그려진 엑셀 데이터를 활용해 데이터를 정리하여 카르토그램과 지도에 시각화해보았다.
📖 27일차 공부 내용 자세히
1. 네이버 API
■ 네이버 검색 AP
- 검색 API는 네이버 검색 결과를 뉴스, 백과사전, 블로그, 쇼핑, 영화, 웹 문서, 전문정보, 지식iN, 책, 카페글 등 분야별로 볼 수 있는 API이다
- 검색 결과를 XML 형식 또는 JSON 형식으로 반환한다
- API를 호출 할 때는 검색어와 검색 조건을 쿼리 스트링(Query String)형식의 데이터로 전달한다
■ 몰스킨 상품 검색 데이터 정리/시각화
진행순서
- gen_search_url (generate url)
- gen_result_onpage (get data on one page)
- get_fields (convert pandas data frame)
- actMain (all data gethering)
- toExcel (export to excel)
❶ gen_search_url() ⇒ url 생성
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을 챙겨봐서 오늘도 새로운 화가 공개되는 날이라 시청했는데, 매 화를 볼 때마다 가지고 있는 힘과 체력도 중요하지만 집념도 그에 못지않게 필요하다는 걸 느낀다.
쉬이 포기하지 말고, 안주하지 말고 끈기있게 해보자!
※본 내용은 제로베이스 데이터 취업 스쿨에서 제공하는 학습 내용에 기반합니다.
'제로베이스 데이터 스쿨 > 일일 스터디 노트' 카테고리의 다른 글
29일차 스터디노트 / 파이썬 EDA Level 테스트 1회차 (0) | 2023.02.14 |
---|---|
28일차 스터디노트 / SQL 문법, Database, Table, SELECT, ORDER BY, 비교연산자, 논리연산자 (0) | 2023.02.08 |
26일차 스터디노트 / 파이썬 웹데이터 수집, 파이썬 Selenium, 주유 가격 정보 시각화, boxplot, folium (0) | 2023.02.07 |
25일차 스터디노트 / 파이썬 웹데이터 수집하고 정리하기, Beutiful Soup, 네이버금융·위키백과·시카고 맛집·네이버영화 데이터 수집 및 정리 (0) | 2023.02.06 |
24일차 스터디노트 / 파이썬 Seaborn, Folium, 지도 시각화, 데이터 시각화 (0) | 2023.02.03 |