# 아래 코드가 set_matplotlib_hangul.py
# matplolib 한글 대응 작업
import platform
import matplotlib.pyplot as plt
from matplotlib import font_manager, rc
path = "c:/Windows/Fonts/malgun.ttf"
if platform.system() == "Darwin":
print("Hangul OK in your MAC !!!")
rc("font", family="AppleGothic")
elif platform.system() == "Windows":
font_name = font_manager.FontProperties(fname=path).get_name()
print("Hangul OK in your Windows !!!")
rc("font", family=font_name)
else:
print("Unknown system... sorry~~~~")
plt.rcParams["axes.unicode_minus"] = False
---------------------------------
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import set_matplotlib_hangul
import warnings
warnings.filterwarnings(action="ignore")
%matplotlib inline
>> Hangul OK in your Windows !!!
# 위 엑셀로 보면 제목 행이 2개이다.
population = pd.read_excel("../data/07_population_raw_data.xlsx")
population
# 엑셀의 제목 행 2줄(0번째, 1번째) 중에서 1번째만 나타내겠다.
population = pd.read_excel("../data/07_population_raw_data.xlsx", header=1)
population
- fillna 메서드는 DataFrame에서 결측값을 원하는 값으로 변경하는 메서드입니다.
- 결측값 변경 (fillna / backfill / bfill / pad / ffill)
- DataFrame.fillna(value=None, method=None, axis=None, inplace=False, limit=None, downcast=None)
- DataFrame.backfill( ) / DataFrame.bfill : DataFrame.fillna(mathod='bfill')과 동일합니다.
- DataFrame.pad / DataFrame.ffill : DataFrame.fillna(method='ffill')과 동일합니다.
# 기본 사용법
df.fillna(value=None, method=None, axis=None, inplace=False, limit=None, downcast=None)
value : 결측값을 대체할 값입니다. dict형태로도 가능합니다.
method : 결측값을 변경할 방식입니다.
bfill, backfill 로 할경우 결측값을 바로 아래 값과 동일하게 변경합니다.
ffill, pad 로 할 경우 결측값을 바로 위 값과 동일하게 변경합니다.
axis : {0 : index / 1 : columns} fillna 메서드를 적용할 레이블입니다.
inplace : 원본을 변경할지 여부입니다. True일 경우 원본을 변경하게 됩니다.
limit : 결측값을 변경할 횟수입니다. 위에서부터 limit로 지정된 갯수만큼만 변경합니다.
downcast : 다운캐스트할지 여부입니다. downcast='infer'일 경우 float64를 int64로 변경합니다.
-----------------------------------
# 실험할 데이터를 만든다
datas = {
"A" : np.random.randint(1, 45, 8),
"B" : np.random.randint(1, 45, 8),
"C" : np.random.randint(1, 45, 8)
}
datas
>>
{'A': array([16, 26, 5, 34, 34, 19, 14, 4]),
'B': array([34, 18, 36, 41, 13, 35, 40, 39]),
'C': array([33, 6, 19, 38, 8, 19, 15, 9])}
--------------------------------------
# 데이터 프레임으로 만든다
fillna_df = pd.DataFrame(datas)
fillna_df
>>
A B C
0 16 34 33
1 26 18 6
2 5 36 19
3 34 41 38
4 34 13 8
5 19 35 19
6 14 40 15
7 4 39 9
--------------------------
# NaN 값을 여기저기에 만든다
fillna_df.loc[2:4, ["A"]] = np.NaN
fillna_df.loc[3:5, ["B"]] = np.NaN
fillna_df.loc[4:7, ["C"]] = np.NaN
fillna_df
>>
# fillna()를 써서 NaN 값을 채워보자
# 옵션을 줄 수 있는데 제일 기본 옵션은 value= 옵션이다
fillna_df.fillna(value=1) # 1로 모두 채워라
>>
# fillna()를 써서 NaN 값을 채워보자
# method="pad" : 앞의 값을 가져와서 채워준다
fillna_df.fillna(method="pad")
>>
# fillna()를 써서 NaN 값을 채워보자
# method="backfill" : 뒤의 값을 가져와서 채워준다
# C 컬럼은 뒤의 값이 없으므로 그대로 유지된다.
fillna_df.fillna(method="backfill")
>>
# fillna()를 써서 NaN 값을 채워보자
# method="pad" : 앞의 값을 가져와서 채워준다
# 기본이 세로 기준이고,
# axis=1 을 주면 가로기준으로 바뀐다
fillna_df.fillna(method="pad", axis=1)
>>
# 엑셀을 읽어오자
# 실제 엑셀을 보면 '전국'이 3개 셀을 다 차지하고 있어서 NanN 값이 있었음 > 이부분도 캡처해서 올리자
population = pd.read_excel("../data/07_population_raw_data.xlsx", header=1)
population.fillna(method="pad", inplace=True) # fillna: NaN 이 있다면 채워라
population.head()
# non-null : NaN 값이 없다는 뜻?
population.info()
>>
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 846 entries, 0 to 845
Data columns (total 16 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 행정구역(동읍면)별(1) 846 non-null object
1 행정구역(동읍면)별(2) 846 non-null object
2 항목 846 non-null object
3 계 846 non-null float64
4 20 - 24세 846 non-null float64
5 25 - 29세 846 non-null float64
6 30 - 34세 846 non-null int64
7 35 - 39세 846 non-null float64
8 65 - 69세 846 non-null float64
9 70 - 74세 846 non-null float64
10 75 - 79세 846 non-null int64
11 80 - 84세 846 non-null float64
12 85 - 89세 846 non-null float64
13 90 - 94세 846 non-null float64
14 95 - 99세 846 non-null int64
15 100+ 846 non-null float64
dtypes: float64(10), int64(3), object(3)
memory usage: 105.9+ KB
-----------------------------
# 일부 컬럼의 이름도 바꿔주자
population.rename(
columns={"행정구역(동읍면)별(1)":"광역시도", "행정구역(동읍면)별(2)":"시도", "계":"인구수"}, inplace=True
)
population.head()
# 소계는 제거하자
# ["시도"] != "소계" 인 것들만 추려낸다
# 자동적으로 광역시도 컬럼도 "전국" 인 컬럼은 걸려졌다
population = population[(population["시도"] != "소계")]
population.head()
population.rename(columns={"항목":"구분"}, inplace=True)
population.head()
population.loc[population["구분"] == "총인구수 (명)"]
population.loc[population["구분"] == "총인구수 (명)", "구분"]
# 총인구수 (명) => 합계로 바꾸기
population.loc[population["구분"] == "총인구수 (명)", "구분"] = "합계"
population.head()
# 컬럼명이 길어서 컬럼명 변경
population.is_copy = False
# 위는 현재로서는 안해도 상관은 없음
# copy를 했을 때 warning 을 내보내지 말아달라는 뜻
# 남자인구수 (명) => 남자
# 여자인구수 (명) => 여자 로 바꾸기
population.loc[population["구분"] == "남자인구수 (명)", "구분"] = "남자"
population.loc[population["구분"] == "여자인구수 (명)", "구분"] = "여자"
population.head()
# 소멸지역을 조사하기 위한 데이터를 만들어두자
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+"]
)
population.head()
# 데이터의 모양이 가로에 지역
# 세로에 연령대별, 남여구분이 모두 위치하도록 변경 >> 판다스 피봇테이블
# values 목록별로 columns 구분이 나타난다
#
pop = pd.pivot_table(
data = population,
index=["광역시도", "시도"],
columns=["구분"],
values=["인구수", "20 - 39세", "65세 이상"]
)
pop
# 소멸비율계산
# 인구소멸위기 지역의 정의 > 소멸 위험 지역의 정의
# 65세 이상 노인 인구와 20~39세 여성 인구를 비교해
# 젊은 여성 인구가 노인 인구의 절반에 미달할 경우
# 드디어 소멸 비율을 계산할 수 있게 되었다.
pop["소멸비율"] = pop["20 - 39세", "여자"] / (pop["65세 이상", "합계"] / 2)
pop.head()
# 소멸위기지역 컬럼 생성
# 소멸 위기 지역인지도 체크해 둘 수 있다.
pop["소멸위기지역"] = pop["소멸비율"] < 1.0
pop
# 소멸위기지역 조회
pop["소멸위기지역"] == True
pop[pop["소멸위기지역"] == True] # True 인 곳만 나온다
# 소멸위기지역을 바로 조회해볼 수 있다. 대부분 '군'지역
# 시도가 현재 컬럼이 아닌 인덱스로 들어가 있으므로 .index
# get_level_values(0) : 광역시도
# get_level_values(1) : 시도
pop[pop["소멸위기지역"] == True].index.get_level_values(1)
>>
Index(['고성군', '삼척시', '양양군', '영월군', '정선군', '평창군', '홍천군', '횡성군', '가평군', '양평군',
'연천군', '거창군', '고성군', '남해군', '밀양시', '산청군', '의령군', '창녕군', '하동군', '함안군',
'함양군', '합천군', '고령군', '군위군', '문경시', '봉화군', '상주시', '성주군', '영덕군', '영양군',
'영주시', '영천시', '예천군', '울릉군', '울진군', '의성군', '청도군', '청송군', '동구', '영도구',
'강화군', '옹진군', '강진군', '고흥군', '곡성군', '구례군', '담양군', '보성군', '신안군', '영광군',
'영암군', '완도군', '장성군', '장흥군', '진도군', '함평군', '해남군', '화순군', '고창군', '김제시',
'남원시', '무주군', '부안군', '순창군', '임실군', '장수군', '정읍시', '진안군', '공주시', '금산군',
'논산시', '보령시', '부여군', '서천군', '예산군', '청양군', '태안군', '홍성군', '괴산군', '단양군',
'보은군', '영동군', '옥천군'],
dtype='object', name='시도')
---------------------------
pop[pop["소멸위기지역"] == True].index.get_level_values(0)
>>
Index(['강원도', '강원도', '강원도', '강원도', '강원도', '강원도', '강원도', '강원도', '경기도', '경기도',
'경기도', '경상남도', '경상남도', '경상남도', '경상남도', '경상남도', '경상남도', '경상남도', '경상남도',
'경상남도', '경상남도', '경상남도', '경상북도', '경상북도', '경상북도', '경상북도', '경상북도', '경상북도',
'경상북도', '경상북도', '경상북도', '경상북도', '경상북도', '경상북도', '경상북도', '경상북도', '경상북도',
'경상북도', '부산광역시', '부산광역시', '인천광역시', '인천광역시', '전라남도', '전라남도', '전라남도',
'전라남도', '전라남도', '전라남도', '전라남도', '전라남도', '전라남도', '전라남도', '전라남도', '전라남도',
'전라남도', '전라남도', '전라남도', '전라남도', '전라북도', '전라북도', '전라북도', '전라북도', '전라북도',
'전라북도', '전라북도', '전라북도', '전라북도', '전라북도', '충청남도', '충청남도', '충청남도', '충청남도',
'충청남도', '충청남도', '충청남도', '충청남도', '충청남도', '충청남도', '충청북도', '충청북도', '충청북도',
'충청북도', '충청북도'],
dtype='object', name='광역시도')
------------------------------------
# 다음 단계를 위해서 인덱스를 새로 잡음
# reset_index > 인덱스를 리셋함
# 인덱스를 리셋해서 '시도' 데이터가 컬럼으로 빠져서 데이터로 사용할 수 있게 됨
# 광역시도, 시도가 인덱스에서 빠져서 컬럼으로 들어왔다
# 구분이라는 인덱스가 새로 생김
pop.reset_index(inplace=True)
pop.head()
pop.columns # MultiIndex 이다.
>>
MultiIndex([( '광역시도', ''),
( '시도', ''),
('20 - 39세', '남자'),
('20 - 39세', '여자'),
('20 - 39세', '합계'),
( '65세 이상', '남자'),
( '65세 이상', '여자'),
( '65세 이상', '합계'),
( '인구수', '남자'),
( '인구수', '여자'),
( '인구수', '합계'),
( '소멸비율', ''),
( '소멸위기지역', '')],
names=[None, '구분'])
------------------------------------
# 컬럼의 길이 (광역시도 ~ 소멸위기지역까지 컬럼 13개)
len(pop.columns.get_level_values(0))
>> 13
-----------------------------------
pop.columns.get_level_values(0)
>>
Index(['광역시도', '시도', '20 - 39세', '20 - 39세', '20 - 39세', '65세 이상', '65세 이상',
'65세 이상', '인구수', '인구수', '인구수', '소멸비율', '소멸위기지역'],
dtype='object')
-----------------------------------
pop.columns.get_level_values(0)[0]
>> '광역시도'
-----------------------------------
pop.columns.get_level_values(1)
>>
Index(['', '', '남자', '여자', '합계', '남자', '여자', '합계', '남자', '여자', '합계', '', ''], dtype='object', name='구분')
-----------------------------------
# 컬럼명 정리
# 컬럼명이 총 2줄인데 합쳐서 1줄로 만든다
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
pop.head()
pop.info()
>>
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 264 entries, 0 to 263
Data columns (total 13 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 광역시도 264 non-null object
1 시도 264 non-null object
2 20 - 39세남자 264 non-null float64
3 20 - 39세여자 264 non-null float64
4 20 - 39세합계 264 non-null float64
5 65세 이상남자 264 non-null float64
6 65세 이상여자 264 non-null float64
7 65세 이상합계 264 non-null float64
8 인구수남자 264 non-null float64
9 인구수여자 264 non-null float64
10 인구수합계 264 non-null float64
11 소멸비율 264 non-null float64
12 소멸위기지역 264 non-null bool
dtypes: bool(1), float64(10), object(2)
memory usage: 25.1+ KB
---------------------------------
# 43 44 45
pop["시도"].unique()
>>
array(['강릉시', '고성군', '동해시', '삼척시', '속초시', '양구군', '양양군', '영월군', '원주시',
'인제군', '정선군', '철원군', '춘천시', '태백시', '평창군', '홍천군', '화천군', '횡성군',
'가평군', '고양시', '과천시', '광명시', '광주시', '구리시', '군포시', '권선구', '기흥구',
'김포시', '남양주시', '단원구', '덕양구', '동두천시', '동안구', '만안구', '부천시', '분당구',
'상록구', '성남시', '소사구', '수원시', '수정구', '수지구', '시흥시', '안산시', '안성시',
'안양시', '양주시', '양평군', '여주시', '연천군', '영통구', '오산시', '오정구', '용인시',
'원미구', '의왕시', '의정부시', '이천시', '일산동구', '일산서구', '장안구', '중원구', '처인구',
'파주시', '팔달구', '평택시', '포천시', '하남시', '화성시', '거제시', '거창군', '김해시',
'남해군', '마산합포구', '마산회원구', '밀양시', '사천시', '산청군', '성산구', '양산시', '의령군',
'의창구', '진주시', '진해구', '창녕군', '창원시', '통영시', '하동군', '함안군', '함양군',
'합천군', '경산시', '경주시', '고령군', '구미시', '군위군', '김천시', '남구', '문경시',
'봉화군', '북구', '상주시', '성주군', '안동시', '영덕군', '영양군', '영주시', '영천시',
'예천군', '울릉군', '울진군', '의성군', '청도군', '청송군', '칠곡군', '포항시', '광산구',
'동구', '서구', '달서구', '달성군', '수성구', '중구', '대덕구', '유성구', '강서구', '금정구',
'기장군', '동래구', '부산진구', '사상구', '사하구', '수영구', '연제구', '영도구', '해운대구',
'강남구', '강동구', '강북구', '관악구', '광진구', '구로구', '금천구', '노원구', '도봉구',
'동대문구', '동작구', '마포구', '서대문구', '서초구', '성동구', '성북구', '송파구', '양천구',
'영등포구', '용산구', '은평구', '종로구', '중랑구', '세종특별자치시', '울주군', '강화군', '계양구',
'남동구', '부평구', '연수구', '옹진군', '강진군', '고흥군', '곡성군', '광양시', '구례군',
'나주시', '담양군', '목포시', '무안군', '보성군', '순천시', '신안군', '여수시', '영광군',
'영암군', '완도군', '장성군', '장흥군', '진도군', '함평군', '해남군', '화순군', '고창군',
'군산시', '김제시', '남원시', '덕진구', '무주군', '부안군', '순창군', '완산구', '완주군',
'익산시', '임실군', '장수군', '전주시', '정읍시', '진안군', '서귀포시', '제주시', '계룡시',
'공주시', '금산군', '논산시', '당진시', '동남구', '보령시', '부여군', '서북구', '서산시',
'서천군', '아산시', '예산군', '천안시', '청양군', '태안군', '홍성군', '괴산군', '단양군',
'보은군', '상당구', '서원구', '영동군', '옥천군', '음성군', '제천시', '증평군', '진천군',
'청원구', '청주시', '충주시', '흥덕구'], dtype=object)
-------------------------------------
# len(pop) 은 264
si_name = [None] * len(pop)
si_name
>>
[None,
None,
None,
None,
None,
None
...
None,
None,
None,
None]
---------------------------------
# 자치구는 어차피 자료에서 나타나니, 행정구를 가진 지역만 신경쓰면 된다
tmp_gu_dict = {
"수원": ["장안구", "권선구", "팔달구", "영통구"],
"성남": ["수정구", "중원구", "분당구"],
"안양": ["만안구", "동안구"],
"안산": ["상록구", "단원구"],
"고양": ["덕양구", "일산동구", "일산서구"],
"용인": ["처인구", "기흥구", "수지구"],
"청주": ["상당구", "서원구", "흥덕구", "청원구"],
"천안": ["동남구", "서북구"],
"전주": ["완산구", "덕진구"],
"포항": ["남구", "북구"],
"창원": ["의창구", "성산구", "진해구", "마산합포구", "마산회원구"],
"부천": ["오정구", "원미구", "소사구"]
}
pop.head()
pop["광역시도"].unique()
>>
array(['강원도', '경기도', '경상남도', '경상북도', '광주광역시', '대구광역시', '대전광역시', '부산광역시',
'서울특별시', '세종특별자치시', '울산광역시', '인천광역시', '전라남도', '전라북도', '제주특별자치도',
'충청남도', '충청북도'], dtype=object)
------------------------------
pop["시도"].unique()
>>
array(['강릉시', '고성군', '동해시', '삼척시', '속초시', '양구군', '양양군', '영월군', '원주시',
'인제군', '정선군', '철원군', '춘천시', '태백시', '평창군', '홍천군', '화천군', '횡성군',
'가평군', '고양시', '과천시', '광명시', '광주시', '구리시', '군포시', '권선구', '기흥구',
'김포시', '남양주시', '단원구', '덕양구', '동두천시', '동안구', '만안구', '부천시', '분당구',
'상록구', '성남시', '소사구', '수원시', '수정구', '수지구', '시흥시', '안산시', '안성시',
'안양시', '양주시', '양평군', '여주시', '연천군', '영통구', '오산시', '오정구', '용인시',
'원미구', '의왕시', '의정부시', '이천시', '일산동구', '일산서구', '장안구', '중원구', '처인구',
'파주시', '팔달구', '평택시', '포천시', '하남시', '화성시', '거제시', '거창군', '김해시',
'남해군', '마산합포구', '마산회원구', '밀양시', '사천시', '산청군', '성산구', '양산시', '의령군',
'의창구', '진주시', '진해구', '창녕군', '창원시', '통영시', '하동군', '함안군', '함양군',
'합천군', '경산시', '경주시', '고령군', '구미시', '군위군', '김천시', '남구', '문경시',
'봉화군', '북구', '상주시', '성주군', '안동시', '영덕군', '영양군', '영주시', '영천시',
'예천군', '울릉군', '울진군', '의성군', '청도군', '청송군', '칠곡군', '포항시', '광산구',
'동구', '서구', '달서구', '달성군', '수성구', '중구', '대덕구', '유성구', '강서구', '금정구',
'기장군', '동래구', '부산진구', '사상구', '사하구', '수영구', '연제구', '영도구', '해운대구',
'강남구', '강동구', '강북구', '관악구', '광진구', '구로구', '금천구', '노원구', '도봉구',
'동대문구', '동작구', '마포구', '서대문구', '서초구', '성동구', '성북구', '송파구', '양천구',
'영등포구', '용산구', '은평구', '종로구', '중랑구', '세종특별자치시', '울주군', '강화군', '계양구',
'남동구', '부평구', '연수구', '옹진군', '강진군', '고흥군', '곡성군', '광양시', '구례군',
'나주시', '담양군', '목포시', '무안군', '보성군', '순천시', '신안군', '여수시', '영광군',
'영암군', '완도군', '장성군', '장흥군', '진도군', '함평군', '해남군', '화순군', '고창군',
'군산시', '김제시', '남원시', '덕진구', '무주군', '부안군', '순창군', '완산구', '완주군',
'익산시', '임실군', '장수군', '전주시', '정읍시', '진안군', '서귀포시', '제주시', '계룡시',
'공주시', '금산군', '논산시', '당진시', '동남구', '보령시', '부여군', '서북구', '서산시',
'서천군', '아산시', '예산군', '천안시', '청양군', '태안군', '홍성군', '괴산군', '단양군',
'보은군', '상당구', '서원구', '영동군', '옥천군', '음성군', '제천시', '증평군', '진천군',
'청원구', '청주시', '충주시', '흥덕구'], dtype=object)
# 위 '광역시도'와 '시도'를 합쳐서
# 서울 중구, 남양주, 포항 북구, 안산 단원 이런식으로 만들어준다
# (1) 일반 시의 이름과, 세종시, 그리고 광역시도의 일반 구를 정리한다
for idx, row in pop.iterrows():
# 광역시나 특별시, 자치시가 아닌 경우의 행정구에 대해서만 적용한다
# 광역시도 컬럼의 마지막 3글자가 '광역시', '특별시', '자치시'가 아니라면
# 일반 '도'에 있는 도시이다. 그래서 마지막 한글자를 빼라
# 강릉시 => 강릉
# 춘천시 => 춘천
# 이런식으로 정리한다
if row["광역시도"][-3:] not in ["광역시", "특별시", "자치시"]:
si_name[idx] = row["시도"][:-1] # 진주시 > 진주
# 세종시는 특별히 관리하고
# 세종특별자치시 > 세종
elif row["광역시도"] == "세종특별자치시":
si_name[idx] = "세종"
# 위 두개를 제외하면 나머지는 광역시뿐인데,
# row["광역시도"][:2] 이 부분은 서울, 부산, 대구 이렇게 앞글자 2개만 따고
# row["시도"] 중구 => 중구
# row["시도"][:-1] 강남구 => 강남과 같이 정리한다.
# 결국 서울 서초, 서울 중구 이런식으로
else:
if len(row["시도"]) == 2: # 서구, 동구, 중구...
# 서울 중구, 부산 금정
si_name[idx] = row["광역시도"][:2] + " " + row["시도"]
else:
# 서울 영등포, 서울 서대문
si_name[idx] = row["광역시도"][:2] + " " + row["시도"][:-1]
-------------------------------------
si_name
>>
['강릉',
'고성',
'동해',
'삼척',
'속초',
'양구',
'양양',
'영월',
'원주',
'인제',
'정선',
'철원',
'춘천',
'태백',
'평창',
...
'대전 서구',
'대전 유성',
'대전 중구',
'부산 강서',
'부산 금정',
'부산 기장',
'부산 남구',
'부산 동구',
'부산 동래',
'부산 부산진',
'부산 북구',
'부산 사상',
'부산 사하',
'부산 서구',
'부산 수영',
'부산 연제',
'부산 영도',
'부산 중구',
'부산 해운대',
'서울 강남',
'서울 강동',
'서울 강북',
'서울 강서',
'서울 관악',
'서울 광진',
'서울 구로',
'서울 금천',
'서울 노원',
'서울 도봉',
'서울 동대문',
'서울 동작',
'서울 마포',
'서울 서대문',
'서울 서초',
'서울 성동',
'서울 성북',
'서울 송파',
'서울 양천',
'서울 영등포',
'서울 용산',
'서울 은평',
'서울 종로',
'서울 중구',
'서울 중랑',
'세종',
'울산 남구',
'울산 동구',
'울산 북구',
'울산 울주',
'울산 중구',
'인천 강화',
'인천 계양',
'인천 남구',
'인천 남동',
'인천 동구',
'인천 부평',
'인천 서구',
'인천 연수',
'인천 옹진',
'인천 중구',
'강진',
...
'청원',
'청주',
'충주',
'흥덕']
# 행정구에 대해 특별시 다시 계산한다
for idx, row in pop.iterrows():
# 광역시나 특별시, 자치시가 아닌 경우의 행정구에 대해서만 적용한다
# 광역시도 컬럼의 마지막 3글자가 '광역시', '특별시', '자치시'가 아니라면
# tmp_gu_dict 에서 하나씩 가지고 온다
if row["광역시도"][-3:] not in ["광역시", "특별시", "자치시"]:
# 행정구를 지정한 dict형 자료에 있는 지역인지 검색하고
for keys, values in tmp_gu_dict.items():
# 행정구를 가진 시의 구 이름을 tmp_gu_dict 컬럼이 가지고 있다면
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]
--------------------------------
si_name
>>
['강릉',
'고성',
'동해',
'삼척',
...
'광주',
'구리',
'군포',
'수원 권선',
'용인 기흥',
'김포',
'남양주',
'안산 단원',
'고양 덕양',
'동두천',
'안양 동안',
'안양 만안',
'부천',
'성남 분당',
'안산 상록',
'성남',
'부천 소사',
'수원',
'성남 수정',
'용인 수지',
'시흥',
'안산',
'안성',
...
'태안',
'홍성',
'괴산',
'단양',
'보은',
'청주 상당',
'청주 서원',
'영동',
'옥천',
'음성',
'제천',
'증평',
'진천',
'청주 청원',
'청주',
'충주',
'청주 흥덕']
# 특별히 고성군에 대해 한번 더 신경써준다(강원 고성, 경남 고성)
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] = "고성(경남)"
---------------------
si_name
>>
['강릉',
'고성(강원)',
'동해',
'삼척',
'속초',
'양구',
'양양',
'영월',
...
'의정부',
'이천',
'고양 일산동',
'고양 일산서',
'수원 장안',
'성남 중원',
'용인 처인',
'파주',
'수원 팔달',
'평택',
'포천',
'하남',
'화성',
'거제',
'거창',
'고성(경남)',
'김해',
'남해',
'창원 합포',
'창원 회원',
'밀양',
'사천',
'산청',
'창원 성산',
'양산',
...
'단양',
'보은',
'청주 상당',
'청주 서원',
'영동',
'옥천',
'음성',
'제천',
'증평',
'진천',
'청주 청원',
'청주',
'충주',
'청주 흥덕']
-------------------------
# ID 란 컬럼을 만들어주고 여기에 넣어준다
pop["ID"] = si_name
pop
# 인구 소멸 위기 지역 파악도 끝냈고,
# 지도에 그리기 위한 ID도 다 생성했다.
# 필요없는 컬럼은 없애준다
del pop["20 - 39세남자"]
del pop["65세 이상남자"]
del pop["65세 이상여자"]
pop.head()
# 엑셀에서 그린 지도 모양을 읽어온다
draw_korea_raw = pd.read_excel("../data/07_draw_korea_raw.xlsx")
draw_korea_raw
# 각 지역별 위치가 나타났다
# 0번 인덱스에는 7, 8 , 9, 10에 값이 채워져있고, 값은 철원~~ 등등이다
draw_korea_raw_stacked = pd.DataFrame(draw_korea_raw.stack())
draw_korea_raw_stacked
# 인덱스로 나타난 좌표를 데이터로 사용하기 위해 reset_index
draw_korea_raw_stacked.reset_index(inplace=True)
draw_korea_raw_stacked
# 이름도 바꿔주고
draw_korea_raw_stacked.rename(
columns={"level_0":"y", "level_1":"x", 0:"ID"}, inplace=True
)
draw_korea_raw_stacked
# 변수명도 그냥 바꿔준다
draw_korea = draw_korea_raw_stacked
----------------------------
# 경계선을 수작업으로 만들어준다...
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, 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)]
]
------------------------------
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, 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)]]
--------------------------------------
# 시도의 이름을 표현하는 함수
# 현재는 데스트용.
# dispname는 실제로 나타낼 이름을 의미한다
def plot_text_simple(draw_korea):
for idx, row in draw_korea.iterrows():
# 공백으로 나눴을때 길이가 2이면 : 서울 중구, 용인 기흥 ...
if len(row["ID"].split()) == 2:
# {}\n{} : 다음 줄로 내려주라는 뜻(?) 엑셀 캡처처럼 서울\n마포 이런식으로 표현하기 위함
dispname = "{}\n{}".format(row["ID"].split()[0], row["ID"].split()[1])
# 강원고성, 경남고성은 위치적으로는 구분이 가능하므로 그냥 고성
elif row["ID"][:2] == "고성":
dispname = "고성"
else:
dispname = row["ID"] # 화성, 시흥, 진주,,
# annotate 은 주석을 달기 위한 기능(글자를 나타내기 위함)
# dispname의 글자를 쓴다
plt.annotate(
dispname,
(row["x"] + 0.5, row["y"] + 0.5), # 경계에 글자가 있으면 안되므로 띄운다
weight = "bold"
)
-------------------------------------------------
# 간단히 경계선과 시도 이름만 먼저 도전
def simpleDraw(draw_korea):
plt.figure(figsize=(8, 11))
plot_text_simple(draw_korea)
for path in BORDER_LINES:
# BORDER_LINES 은 x, y 의 연속인데
# 이걸 plot 으로 나타내면 x끼리, y끼리 모아야됨
ys, xs = zip(*path)
print(ys, xs) # y끼리, x끼리 모여있음
-----------------------------------------------
simpleDraw(draw_korea)
# 아래 (5, 5, 7, 7, 11, 11)은 BORDER_LINES에서 인천의 x좌표만 모은 것임
# 아래 (1, 2, 2, 3, 3, 0)은 BORDER_LINES에서 인천의 y좌표만 모은 것임
>>
(5, 5, 7, 7, 11, 11) (1, 2, 2, 3, 3, 0)
(5, 5, 2, 2, 4, 4, 7, 7, 9, 9, 10, 10, 5) (4, 5, 5, 7, 7, 9, 9, 7, 7, 5, 5, 4, 4)
(1, 1, 3, 3, 10, 10, 12, 12, 11, 11, 12, 12, 11, 11) (7, 8, 8, 10, 10, 7, 7, 6, 6, 5, 5, 4, 4, 3)
(8, 8, 6, 6) (10, 11, 11, 12)
(12, 13, 13, 14, 14, 15, 15, 16, 16) (5, 5, 4, 4, 5, 5, 4, 4, 2)
(16, 17, 17, 16, 16, 19, 19, 20, 20, 21, 21, 19, 19) (4, 4, 5, 5, 6, 6, 5, 5, 4, 4, 3, 3, 1)
(13, 13, 16) (5, 6, 6)
(13, 14) (5, 5)
(21, 21, 22, 22, 24, 24, 21) (2, 3, 3, 4, 4, 2, 2)
(20, 21, 21, 23) (5, 5, 6, 6)
(10, 12, 12, 14, 14, 16, 16) (8, 8, 9, 9, 8, 8, 6)
(14, 14, 14, 13, 13) (9, 11, 12, 12, 13)
(15, 17, 17, 16, 16, 14) (8, 8, 10, 10, 11, 11)
(17, 18, 18, 19, 19, 20, 20, 21) (9, 9, 8, 8, 9, 9, 10, 10)
(16, 16) (11, 13)
(27, 27, 25) (5, 6, 6)
# 시도의 이름을 표현하는 함수
# 현재는 데스트용.
# dispname는 실제로 나타낼 이름을 의미한다
def plot_text_simple(draw_korea):
for idx, row in draw_korea.iterrows():
# 공백으로 나눴을때 길이가 2이면 : 서울 중구, 용인 기흥 ...
if len(row["ID"].split()) == 2:
# {}\n{} : 다음 줄로 내려주라는 뜻(?) 엑셀 캡처처럼 서울\n마포 이런식으로 표현하기 위함
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 = 11, 1.2
# annotate 은 주석을 달기 위한 기능(글자를 나타내기 위함)
# dispname의 글자를 쓴다
plt.annotate(
dispname,
(row["x"] + 0.5, row["y"] + 0.5), # 경계에 글자가 있으면 안되므로 띄운다
weight = "bold",
fontsize = fontsize, # 위에서 설정한 fontsize
ha = "center", # 수평 정렬 (좌우 가운데 정렬)
va = "center", # 수직 정렬 (위아래 가운데 정렬)
linespacing = linespacing # 위에서 설정한 linespacing
)
------------------------------------
# 간단히 경계선과 시도 이름만 먼저 도전
def simpleDraw(draw_korea):
plt.figure(figsize=(8, 11))
plot_text_simple(draw_korea)
for path in BORDER_LINES:
# BORDER_LINES 은 x, y 의 연속인데
# 이걸 plot 으로 나타내면 x끼리, y끼리 모아야됨
ys, xs = zip(*path)
plt.plot(xs, ys, c="black", lw= 1.5)
# invert_yaxis() : matplotlib 그래프에서는 y 값이 올라가면 밑에서 위로 올라가는데
# 그런데 엑셀은 0부터 아래로 증가하는 방향이므로 방향이 반대다
plt.gca().invert_yaxis()
plt.axis("off")
plt.tight_layout()
plt.show()
simpleDraw(draw_korea)
# 데이터 검증
# 데이터를 합칠때 ID 를 기준으로 합칠건데, 그 전에 검증을 한다
# 반환 값으로 set() 이 나와야 정상이다.
set(draw_korea["ID"].unique()) - set(pop["ID"].unique())
>> set()
--------------------------
# 근데 반대로 하면 차집합이 있다.
# 차집합은 교환법칙이 성립하지 않는다.
# 광역시는 아닌데 행정구를 가지고 있던 도시들이다.
set(pop["ID"].unique()) - set(draw_korea["ID"].unique())
>>
{'고양', '부천', '성남', '수원', '안산', '안양', '용인', '전주', '창원', '천안', '청주', '포항'}
---------------------------
# 이 경우는 지우면 된다. 왜?
# 이미 행정구별로 인구현황을 잡아놨기 때문(?)
tmp_list = list(set(pop["ID"].unique()) - set(draw_korea["ID"].unique()))
for tmp in tmp_list:
pop = pop.drop(pop[pop["ID"] == tmp].index)
print(set(pop["ID"].unique()) - set(draw_korea["ID"].unique()))
# set() 이 나왔다
>> set()
------------------------------
# 그럼 지도를 그리기 위한 데이터와 인구현황 데이터를 합치자
# ID 를 기준으로 합친다
# how="left" 아래 pop, draw_korea 코드에서 왼쪽 데이터인 pop 을 기준으로 한다
# on=["ID"] : ID 컬럼을 기준으로 합친다
pop = pd.merge(pop, draw_korea, how="left", on=["ID"])
pop.head()
# 그림을 그리기 위한 데이터를 계산하는 함수
# 이 함수는 색상을 만들때 최소값을 흰색으로 한다
# blockedMap은 인구현황이고, targetData 는 그리고 싶은 컬럼
# blockedMap : 인구현황(pop)
# targetData : 그리고 싶은 컬럼
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
------------------------------------
# 그림을 그리기 위한 데이터를 계산하는 함수
# 이 함수는 색상을 만들때 중간값을 흰색으로 한다
# blockedMap은 인구현황이고, targetData 는 그리고 싶은 컬럼
def get_data_info_for_zero_center(targetData, blockedMap):
whitelabelmin = 5
tmp_max = max(
[np.abs(min(blockedMap[targetData])), np.abs(max(blockedMap[targetData]))]
)
vmin, vmax = -tmp_max, tmp_max
mapdata = blockedMap.pivot_table(index="y", columns="x", values=targetData)
return mapdata, vmax, vmin, whitelabelmin
--------------------------------------------
# dispname는 실제로 나타낼 이름을 의미한다
def plot_text(targetData, blockedMap, whilelabelmin):
for idx, row in blockedMap.iterrows():
# 공백으로 나눴을때 길이가 2이면 : 서울 중구, 용인 기흥 ...
if len(row["ID"].split()) == 2:
# {}\n{} : 다음 줄로 내려주라는 뜻(?) 엑셀 캡처처럼 서울\n마포 이런식으로 표현하기 위함
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 = 11, 1.2
annocolor = "white" if np.abs(row[targetData]) > whilelabelmin else "black"
# annotate 은 주석을 달기 위한 기능(글자를 나타내기 위함)
# dispname의 글자를 쓴다
plt.annotate(
dispname,
(row["x"] + 0.5, row["y"] + 0.5), # 경계에 글자가 있으면 안되므로 띄운다
weight = "bold",
color = annocolor,
fontsize = fontsize, # 위에서 설정한 fontsize
ha = "center", # 수평 정렬 (좌우 가운데 정렬)
va = "center", # 수직 정렬 (위아래 가운데 정렬)
linespacing = linespacing # 위에서 설정한 linespacing
)
------------------------------------------
def drawKorea(targetData, blockedMap, cmapname, zeroCenter=False):
# zeroCenter=True 이면 아래가 실행됨
if zeroCenter:
masked_mapdata, vmax, vmin, whitelabelmin = get_data_info_for_zero_center(targetData, blockedMap)
# zeroCenter=False 이면 아래가 실행됨
if not zeroCenter:
masked_mapdata, vmax, vmin, whitelabelmin = get_data_info(targetData, blockedMap)
plt.figure(figsize=(9,11))
plt.pcolor(
masked_mapdata, vmin=vmin, vmax=vmax, cmap=cmapname, edgecolor="#aaaaaa", linewidth=0.5
)
plot_text(targetData, blockedMap, whitelabelmin)
for path in BORDER_LINES:
# BORDER_LINES 은 x, y 의 연속인데
# 이걸 plot 으로 나타내면 x끼리, y끼리 모아야됨
ys, xs = zip(*path)
plt.plot(xs, ys, c="black", lw=2)
plt.gca().invert_yaxis()
plt.axis("off")
cb=plt.colorbar(shrink=0.1, aspect=10)
cb.set_label(targetData)
plt.tight_layout()
plt.show()
--------------------------------------
drawKorea("인구수합계", pop, "Blues")
pop["소멸위기지역"] = [1 if con else 0 for con in pop["소멸위기지역"]]
drawKorea("소멸위기지역", pop, "Reds")
pop["여성비"] = (pop["인구수여자"] / pop["인구수합계"] - 0.5) * 100
drawKorea("여성비", pop, "RdBu", zeroCenter=True)
pop["2030여성비"] = (pop["20 - 39세여자"] / pop["20 - 39세합계"] - 0.5) * 100
drawKorea("2030여성비", pop, "RdBu", zeroCenter=True)
import folium
import json
pop_folium = pop.set_index("ID") # ID 가 인덱스로 들어감
pop_folium.head()
geo_path = "../data/07_skorea_municipalities_geo_simple.json"
geo_str = json.load(open(geo_path, encoding="utf-8"))
mymap = folium.Map(location=[36.2002, 127.054], zoom_start=7)
# choropleth : 경계선 그리기
mymap.choropleth(
geo_data = geo_str,
data = pop_folium["인구수합계"],
columns = [pop_folium.index, pop_folium["인구수합계"]],
fill_color = "YlGnBu", # PuRd, YlGnBu
key_on = "feature.id"
)
mymap
mymap = folium.Map(location=[36.2002, 127.054], zoom_start=7)
mymap.choropleth(
geo_data = geo_str,
data = pop_folium["소멸위기지역"],
columns = [pop_folium.index, pop_folium["소멸위기지역"]],
fill_color = "PuRd", # PuRd, YlGnBu
key_on = "feature.id"
)
mymap
# 데이터 저장
draw_korea.to_csv("../data/the-end", encoding="utf-8", sep=",")
위 글은 제로베이스 데이터 취업 스쿨의 강의자료를 참고하여 작성되었습니다.