ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 파이썬으로 데이터 주무르기 - 챕터 1 실습
    코딩 및 데이터분석/데이터 분석(pandas, numpy 등) 2023. 7. 23. 21:35

    책 정보: https://www.yes24.com/Product/Goods/57670268

     

    파이썬으로 데이터 주무르기 - YES24

    독특한 예제를 통해 배우는 데이터 분석 입문이 책은 누구나 한 권 이상 가지고 있을 파이썬 기초 문법책과 같은 내용이 아닌, 데이터 분석이라는 특별한 분야에서 초보를 위해 처음부터 끝까지

    www.yes24.com

     

     

    나는 실습 위주로 부딪히면서 배우는 걸 좋아하는 편이라 기초부터 가르치는 책이 별로 재미가 없었다.

    그런데 이 책은 바로 실전으로 들어가서 너무 좋다.

     

    2017년 기준으로 작성된 것 같은데, 지금 기준 데이터랑 다른 점이 있어서 그걸 해결하면서 하다보니 한 줄 한 줄 이해도 더 잘 되는 느낌이다.

     

    나 같은 성향을 지닌 분들에게는 강추하는 책.

     

    이걸 보면서 절실히 느낀 건.... 고등학생 때 수학 공부 좀 열심히 할 걸... 후회된다는 거

     

    ※ 주의 : 아래 내용은 저 나름대로 코드를 수정하면서 진행했기 때문에 교재 내용이랑 살짝 차이가 있습니다.

     

     

    점선과의 거리가 멀수록 일반적인 CCTV 갯수보다 많거나 적음을 의미한다.


    import pandas as pd
    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib.font_manager as fm
    import platform
    

    서울시 각 지역구 CCTV 현황

    In [153]:
    #csv 파일 읽기 -- 인코딩은 utf-8가 안 돼서 cp949로 인코딩
    CCTV_Seoul = pd.read_csv("C:\\Users\\phant\\Downloads\\CCTV_in_Seoul.csv", encoding='cp949')
    
    #읽는 범위에 결측치밖에 없는 컬럼이 모두 포함되는 바람에 16384개 컬럼이 읽힘. 이에 결측치만 있는 컬럼을 모두 제거
    CCTV_Seoul=CCTV_Seoul.dropna(axis=1)
    
    #1열 컬럼명을 '지역구'로 변경
    CCTV_Seoul.rename(columns={CCTV_Seoul.columns[0] : '지역구'}, inplace=True)
    
    #3열 컬럼명을 '2013년 이전 CCTV'로 변경
    CCTV_Seoul.rename(columns={CCTV_Seoul.columns[2] : '2013년 미만'}, inplace=True)
    
    #오름차순, 내림차순 정렬
    ## '총계' 컬럼 기준 오름차순 정렬 --최소 3개구 종로구, 도봉구, 중구 ;;  최다 3개구 강남구, 관악구, 서초구
    CCTV_Seoul.sort_values(by='총계', ascending=True)
    
    
    #기타 특수 문자 제거 후 정수형으로 바꾸기. fillna(0)은 결측치를 NaN이 아닌 0으로 처리하라는 것
    ## 셀값을 object로 인식하고 있어서 집계 함수를 쓸 수가 없기 때문에 데이터타입을 바꿔주는 것
    CCTV_Seoul.loc[CCTV_Seoul.any(axis=1)] = CCTV_Seoul.replace('[\$,]', '', regex=True).astype(int, errors='ignore').fillna(0)
    
    #2013년 이후 증가한 CCTV 합계 컬럼 생성
    ## [: , 3: ]에서 왼쪽 콜론만 있는 것은 전체 행을 의미. '3: '은 4번째 열부터 마지막 열까지를 의미
    CCTV_Seoul['2013년 이후 증가량'] = CCTV_Seoul.iloc[:, 3:].sum(axis=1)
    
    #2013년 이전 CCTV 갯수 대비 2013년 이후 증가한 CCTV 갯수의 비율 구하기
    ## 2013년 이전 CCTV가 0인 행이 포함되어 있어 'ZeroDivisionError' 에러가 나기에, '2013년 미만' 컬럼에서 값이 0인 곳을 제외하고 나눗셈
    ## 값이 0인 곳은 결과를 NaN으로 처리
    CCTV_Seoul['2013년 이후 CCTV증가율'] = np.where(CCTV_Seoul['2013년 미만'] != 0,
                                       (CCTV_Seoul['2013년 미만'] / CCTV_Seoul['2013년 이후 증가량']) * 100,
                                       np.nan)
    #첫번째 '계' 행 제거
    CCTV_Seoul.drop([0], inplace=True)
    CCTV_Seoul
    
    Out[153]:
    지역구총계2013년 미만2013년2014년2015년2016년2017년2018년2019년2020년2021년2022년2013년 이후 증가량2013년 이후 CCTV증가율12345678910111213141516171819202122232425
    종로구 1980 36 540 107 161 131 158 152 69 250 85 291 1944.0 1.851852
    중 구 2584 130 87 77 236 240 372 386 155 361 403 137 2454.0 5.297474
    용산구 2847 44 50 68 83 295 491 115 322 623 422 334 2803.0 1.569747
    성동구 4047 58 99 110 366 279 945 459 647 485 367 232 3989.0 1.453998
    광진구 3480 507 82 84 64 21 465 443 709 172 662 271 2973.0 17.053481
    동대문구 2759 1 4 12 107 802 711 201 218 223 221 259 2758.0 0.036258
    중랑구 4193 224 331 104 145 153 170 215 1074 976 507 294 3969.0 5.643739
    성북구 4842 137 170 229 322 594 811 867 714 253 407 338 4705.0 2.911796
    강북구 3321 0 21 16 68 210 4 375 963 569 298 797 3321.0 NaN
    도봉구 2247 103 2 79 72 103 117 200 202 183 600 586 2144.0 4.804104
    노원구 2617 77 153 75 510 329 172 216 326 387 220 152 2540.0 3.031496
    은평구 4653 17 44 332 329 555 403 635 1057 288 471 522 4636.0 0.366695
    서대문구 3445 312 144 127 77 254 524 389 344 587 207 480 3133.0 9.958506
    마포구 2628 21 40 109 170 458 376 368 494 298 177 117 2607.0 0.805524
    양천구 3851 197 139 128 239 422 563 830 311 337 338 347 3654.0 5.391352
    강서구 3265 67 59 202 193 168 506 259 458 359 439 555 3198.0 2.095059
    구로구 4693 37 361 193 269 353 563 545 794 651 684 243 4656.0 0.794674
    금천구 2636 0 0 43 361 133 196 539 367 513 207 277 2636.0 NaN
    영등포구 4553 109 81 295 360 285 422 803 153 1357 329 359 4444.0 2.452745
    동작구 2671 66 25 502 127 256 271 300 307 432 30 355 2605.0 2.533589
    관악구 5398 362 244 302 537 607 676 662 890 404 550 164 5036.0 7.188245
    서초구 4995 74 86 70 559 518 1054 426 344 422 566 876 4921.0 1.503759
    강남구 7243 59 62 573 826 1295 989 744 790 923 514 468 7184.0 0.821269
    송파구 3203 84 78 88 221 163 256 516 1081 224 139 353 3119.0 2.693171
    강동구 3190 82 195 56 174 226 351 379 362 635 301 429 3108.0 2.638353

    서울시 인구

    In [152]:
    # 서울시 인구 csv 파일 읽기 -- 이 파일은 원래 utf-8로 인코딩되었었나보다. 이 파일은 오히려 cp949가 안 먹힌다
    ## header는 2번째 행(즉 1)부터 읽으라는 뜻
    ### (인덱스 포함) 1, 3, 4, 5, 7번째 컬럼만 읽기
    
    pop_Seoul=pd.read_csv("C:\\Users\\phant\\Downloads\\Population_in_Seoul.csv",
                          header = 1,
                          usecols = [1,3,4,5,7],
                          encoding='utf-8')
    pop_Seoul=pop_Seoul.dropna(axis=1)
    
    #컬럼명을 보다 직관적으로 변경
    pop_Seoul.rename(columns={pop_Seoul.columns[0] : '지역구', 
                    pop_Seoul.columns[1] : '인구수',
                    pop_Seoul.columns[2] : '한국인',
                    pop_Seoul.columns[3] : '외국인',
                    pop_Seoul.columns[4] : '고령자'},
                    inplace=True)
    
    #'합계' 행 제거
    pop_Seoul.drop([0], inplace=True)
    
    #유니크로 원하는 컬럼의 행에 무슨 값 들어 있는지 알아보기
    # pop_Seoul['지역구'].unique()
    
    #외국인 비율과 고령자 비율 컬럼 추가. 외국인 인구수/지역구 인구수 , 고령자 인구수/지역구 인구수를 백분율로
    pop_Seoul['외국인 비율'] = pop_Seoul['외국인'] / pop_Seoul['인구수'] * 100
    pop_Seoul['고령자 비율'] = pop_Seoul['고령자'] / pop_Seoul['인구수'] * 100
    pop_Seoul
    
    Out[152]:
    지역구인구수한국인외국인고령자외국인 비율고령자 비율12345678910111213141516171819202122232425
    종로구 152212 141060 11152 28265 7.326623 18.569495
    중구 131390 120963 10427 25353 7.935916 19.295989
    용산구 232482 217756 14726 39478 6.334254 16.981100
    성동구 287240 280240 7000 48238 2.436986 16.793622
    광진구 350925 336801 14124 54854 4.024792 15.631260
    동대문구 354884 337574 17310 65154 4.877650 18.359239
    중랑구 389928 385003 4925 76116 1.263054 19.520527
    성북구 441855 430100 11755 78427 2.660375 17.749488
    강북구 296934 292762 4172 67056 1.405026 22.582796
    도봉구 312858 310509 2349 68114 0.750820 21.771539
    노원구 506989 502515 4474 93279 0.882465 18.398624
    은평구 468766 464871 3895 90556 0.830905 19.317954
    서대문구 321966 308437 13529 56785 4.201996 17.636955
    마포구 376542 365570 10972 56582 2.913885 15.026743
    양천구 442345 439219 3126 73522 0.706688 16.620963
    강서구 573711 568287 5424 98659 0.945424 17.196637
    구로구 417983 395183 22800 77259 5.454767 18.483766
    금천구 242467 229307 13160 43772 5.427543 18.052766
    영등포구 398999 376614 22385 66014 5.610290 16.544904
    동작구 390377 380201 10176 68766 2.606711 17.615280
    관악구 502628 487815 14813 83420 2.947110 16.596767
    서초구 408979 404831 4148 63328 1.014233 15.484414
    강남구 537817 532798 5019 83097 0.933217 15.450795
    송파구 663704 658006 5698 105161 0.858515 15.844563
    강동구 464027 459982 4045 79706 0.871717 17.177018

    데이터 병합

    In [188]:
    # 왼쪽 CCTV_Seoul과 오른쪽 pop_Seoul 데이터를 '지역구' 컬럼을 중심으로 merge(SQL의 join)
    data_result=pd.merge(CCTV_Seoul, pop_Seoul, on='지역구')
    
    # 2013년 이후 CCTV 증가율과 총계만 남기고 나머지 CCTV 컬럼 삭제
    data_result.drop(['2013년 미만', '2013년', '2014년', '2015년', '2016년', '2017년', '2018년', '2019년', '2020년', '2021년', '2022년', '2013년 이후 증가량'], axis=1, inplace=True)
    
    #지역구를 index로 설정
    data_result.set_index('지역구', inplace=True)
    
    # object로 되어 있는 컬럼을 int or float으로 변경
    ## astype을 사용할 때는 dtype 바꾼 결과를 다시 원본 df 변수로 새로 할당해줘야 변경이 반경됨
    
    data_result=data_result.astype({'총계':'int64'})
    data_result=data_result.astype({'2013년 이후 CCTV증가율':'float64'})
    data_result.dtypes
    
    Out[188]:
    총계2013년 이후 CCTV증가율인구수한국인외국인고령자외국인 비율고령자 비율지역구종로구용산구성동구광진구동대문구중랑구성북구강북구도봉구노원구은평구서대문구마포구양천구강서구구로구금천구영등포구동작구관악구서초구강남구송파구강동구
    1980 1.851852 152212 141060 11152 28265 7.326623 18.569495
    2847 1.569747 232482 217756 14726 39478 6.334254 16.981100
    4047 1.453998 287240 280240 7000 48238 2.436986 16.793622
    3480 17.053481 350925 336801 14124 54854 4.024792 15.631260
    2759 0.036258 354884 337574 17310 65154 4.877650 18.359239
    4193 5.643739 389928 385003 4925 76116 1.263054 19.520527
    4842 2.911796 441855 430100 11755 78427 2.660375 17.749488
    3321 NaN 296934 292762 4172 67056 1.405026 22.582796
    2247 4.804104 312858 310509 2349 68114 0.750820 21.771539
    2617 3.031496 506989 502515 4474 93279 0.882465 18.398624
    4653 0.366695 468766 464871 3895 90556 0.830905 19.317954
    3445 9.958506 321966 308437 13529 56785 4.201996 17.636955
    2628 0.805524 376542 365570 10972 56582 2.913885 15.026743
    3851 5.391352 442345 439219 3126 73522 0.706688 16.620963
    3265 2.095059 573711 568287 5424 98659 0.945424 17.196637
    4693 0.794674 417983 395183 22800 77259 5.454767 18.483766
    2636 NaN 242467 229307 13160 43772 5.427543 18.052766
    4553 2.452745 398999 376614 22385 66014 5.610290 16.544904
    2671 2.533589 390377 380201 10176 68766 2.606711 17.615280
    5398 7.188245 502628 487815 14813 83420 2.947110 16.596767
    4995 1.503759 408979 404831 4148 63328 1.014233 15.484414
    7243 0.821269 537817 532798 5019 83097 0.933217 15.450795
    3203 2.693171 663704 658006 5698 105161 0.858515 15.844563
    3190 2.638353 464027 459982 4045 79706 0.871717 17.177018

    상관분석

    In [189]:
    # numpy의 corrcoef (상관계수 계산) 명령어로 각 컬럼 간 상관계수를 계산
    
    ## 상관계수 x는 ''-1 <= x <= 1'의 범주를 지님
    ### 0에 가까울수록 약한 상관관계. -1에 가까울수록 역 상관관계(음의 상관관계), 1에 가까울수록 정 상관관계(양의 상관관계)
    
    np.corrcoef(data_result['고령자 비율'], data_result['총계'])
    
    # 고령자 비율과 CCTV 갯수 사이의 관계는 약 -0.32로 약한 음의 상관관계
    
    Out[189]:
    array([[ 1.        , -0.31958939],
           [-0.31958939,  1.        ]])
    In [190]:
    np.corrcoef(data_result['외국인 비율'], data_result['총계'])
    
    # 외국인 비율과 CCTV 갯수 사이의 관계는 약 -0.25로 약한 음의 상관관계
    
    Out[190]:
    array([[ 1.        , -0.25159909],
           [-0.25159909,  1.        ]])
    In [192]:
    np.corrcoef(data_result['인구수'], data_result['총계'])
    
    # 인구수와 CCTV 갯수 사이의 관계는 약 0.44로 약한 양의 상관관계
    
    Out[192]:
    array([[1.        , 0.44096629],
           [0.44096629, 1.        ]])

    그래프 그리기

    In [201]:
    # matplotlib 한글 표기 처리
    import matplotlib.font_manager as fm
    
    plt.rcParams['axes.unicode_minus'] = False
    path = 'C:\\Windows\\Fonts\\malgun.ttf'
    font_name = fm.FontProperties(fname=path).get_name()
    plt.rc('font', family=font_name)
    
    Out[201]:
    총계2013년 이후 CCTV증가율인구수한국인외국인고령자외국인 비율고령자 비율지역구종로구용산구성동구광진구동대문구중랑구성북구강북구도봉구노원구은평구서대문구마포구양천구강서구구로구금천구영등포구동작구관악구서초구강남구송파구강동구
    1980 1.851852 152212 141060 11152 28265 7.326623 18.569495
    2847 1.569747 232482 217756 14726 39478 6.334254 16.981100
    4047 1.453998 287240 280240 7000 48238 2.436986 16.793622
    3480 17.053481 350925 336801 14124 54854 4.024792 15.631260
    2759 0.036258 354884 337574 17310 65154 4.877650 18.359239
    4193 5.643739 389928 385003 4925 76116 1.263054 19.520527
    4842 2.911796 441855 430100 11755 78427 2.660375 17.749488
    3321 NaN 296934 292762 4172 67056 1.405026 22.582796
    2247 4.804104 312858 310509 2349 68114 0.750820 21.771539
    2617 3.031496 506989 502515 4474 93279 0.882465 18.398624
    4653 0.366695 468766 464871 3895 90556 0.830905 19.317954
    3445 9.958506 321966 308437 13529 56785 4.201996 17.636955
    2628 0.805524 376542 365570 10972 56582 2.913885 15.026743
    3851 5.391352 442345 439219 3126 73522 0.706688 16.620963
    3265 2.095059 573711 568287 5424 98659 0.945424 17.196637
    4693 0.794674 417983 395183 22800 77259 5.454767 18.483766
    2636 NaN 242467 229307 13160 43772 5.427543 18.052766
    4553 2.452745 398999 376614 22385 66014 5.610290 16.544904
    2671 2.533589 390377 380201 10176 68766 2.606711 17.615280
    5398 7.188245 502628 487815 14813 83420 2.947110 16.596767
    4995 1.503759 408979 404831 4148 63328 1.014233 15.484414
    7243 0.821269 537817 532798 5019 83097 0.933217 15.450795
    3203 2.693171 663704 658006 5698 105161 0.858515 15.844563
    3190 2.638353 464027 459982 4045 79706 0.871717 17.177018
    In [205]:
    # 수평 막대 그래프, 내림차순 정렬로 지역구 별 CCTV 갯수 그래프 그리기
    
    data_result['총계'].sort_values().plot(kind='barh', grid=True, figsize=(10,10))
    
    # 원본 데이터에 '인구 수 대비 cctv 비율' 컬럼을 만들어서 총계 대신 인구 대비 cctv 비율로 그래프 그리기
    data_result['CCTV비율'] = data_result['총계'] / data_result['인구수'] * 100
    data_result['CCTV비율'].sort_values().plot(kind='barh', grid=True, figsize=(10,10))
    plt.xlabel('CCTV비율')
    plt.show()
    
    In [218]:
    # x축 인구수, y축 총계로 두고 지역구별 scatter 그래프 그리기
    # plt.figure(figsize=(6,6))
    # plt.scatter(data_result['인구수'], data_result['총계'], s=30)
    # plt.ylabel('CCTV 갯수')
    # plt.grid()
    
    # 인구수와 CCTV 갯수 사이 양의 상관관계를 대표하는 직선 긋기
    
    fp1= np.polyfit(data_result['인구수'], data_result['총계'], 1)
    
    ## y축 데이터
    f1 = np.poly1d(fp1)
    
    ## x축 데이터
    fx = np.linspace(100000, 700000, 100)
    
    ### 아까 그린 그래프 위에 직선 추가
    # plt.figure(figsize=(10,10))
    # plt.scatter(data_result['인구수'], data_result['총계'], s=30)
    # plt.plot(fx, f1(fx), ls='dashed', lw=3, color='g' )
    # plt.ylabel('CCTV 갯수')
    # plt.grid()
    # plt.show()
    
    
    # 해당 직선 기울기 상 x축(인구수)에 따른 y값(CCTV)과 실제 설치된 CCTV수의 차이를 절대값으로 나타내는 컬럼을 추가한 뒤
    ## 그 컬럼 기준으로 내림차 순 정렬. 즉 서울시 지역구 별 인구수 대비 CCTV 예상 설치 수 (직선)와 scatter plot에 찍힌 실제 값 사이의 오차가 큰 순서대로 정렬
    
    data_result['오차']=np.abs(data_result['총계']-f1(data_result['인구수']))
    df_sort=data_result.sort_values(by='오차', ascending=False)
    df_sort
    
    Out[218]:
    총계2013년 이후 CCTV증가율인구수한국인외국인고령자외국인 비율고령자 비율CCTV비율오차지역구강남구송파구노원구서초구강서구관악구도봉구동작구마포구성북구구로구성동구영등포구강동구동대문구은평구종로구중랑구금천구용산구서대문구강북구양천구광진구
    7243 0.821269 537817 532798 5019 83097 0.933217 15.450795 1.346741 2899.991076
    3203 2.693171 663704 658006 5698 105161 0.858515 15.844563 0.482595 1717.918763
    2617 3.031496 506989 502515 4474 93279 0.882465 18.398624 0.516185 1584.486730
    4995 1.503759 408979 404831 4148 63328 1.014233 15.484414 1.221334 1243.448079
    3265 2.095059 573711 568287 5424 98659 0.945424 17.196637 0.569102 1242.787621
    5398 7.188245 502628 487815 14813 83420 2.947110 16.596767 1.073955 1216.533327
    2247 4.804104 312858 310509 2349 68114 0.750820 21.771539 0.718217 1063.288950
    2671 2.533589 390377 380201 10176 68766 2.606711 17.615280 0.684210 995.155662
    2628 0.805524 376542 365570 10972 56582 2.913885 15.026743 0.697930 974.643285
    4842 2.911796 441855 430100 11755 78427 2.660375 17.749488 1.095835 939.524125
    4693 0.794674 417983 395183 22800 77259 5.454767 18.483766 1.122773 900.113389
    4047 1.453998 287240 280240 7000 48238 2.436986 16.793622 1.408926 854.315682
    4553 2.452745 398999 376614 22385 66014 5.610290 16.544904 1.141106 847.263296
    3190 2.638353 464027 459982 4045 79706 0.871717 17.177018 0.687460 814.260944
    2759 0.036258 354884 337574 17310 65154 4.877650 18.359239 0.777437 744.217837
    4653 0.366695 468766 464871 3895 90556 0.830905 19.317954 0.992606 626.983714
    1980 1.851852 152212 141060 11152 28265 7.326623 18.569495 1.300817 592.810862
    4193 5.643739 389928 385003 4925 76116 1.263054 19.520527 1.075327 528.905564
    2636 NaN 242467 229307 13160 43772 5.427543 18.052766 1.087158 351.144769
    2847 1.569747 232482 217756 14726 39478 6.334254 16.981100 1.224611 94.306599
    3445 9.958506 321966 308437 13529 56785 4.201996 17.636955 1.069989 92.898927
    3321 NaN 296934 292762 4172 67056 1.405026 22.582796 1.118430 83.813406
    3851 5.391352 442345 439219 3126 73522 0.706688 16.620963 0.870587 53.725320
    3480 17.053481 350925 336801 14124 54854 4.024792 15.631260 0.991665 5.043244
    In [255]:
    #오차를 기준으로 그래프 다시 그리기
    ## 책 저자는 직선에서 멀리 떨어진(즉 오차가 큰) 지역구 10개만 이름을 표기한다고 했는데, 나는 전체적인 게 궁금해서 그냥 다 표기함
    ### 그래서 사실상 오차를 구할 필요가 없었다는...ㅋㅋㅋ
    
    plt.figure(figsize=(14,10))
    plt.scatter(data_result['인구수'], data_result['총계'], c=data_result['오차'], s=50)
    plt.plot(fx, f1(fx), ls='dashed', lw=2, color='black' )
    
    for n in range(0, 24):
        plt.text(df_sort['인구수'][n]*1.01, df_sort['총계'][n]*1.01, df_sort.index[n], fontsize=9.5)
    
    plt.ylabel('CCTV 갯수')
    plt.xlabel('인구수')
    plt.colorbar()
    plt.viridis()
    plt.grid()
    plt.show()
    
Copyright 2023. 준호의 게임 이야기. All rights reserved.