LG U+ why not SW 5/python DA _ exercise

파이썬 데이터 분석 예제 _ 서울시 각 자치구 cctv와 인구 데이터 분석

wangatheringdata 2025. 3. 3. 16:40

 

 

 

 

파이썬

 

 

 

 

 

1.  개요

국감브리핑을 통해 강남3구의 주민들이 거주지 체감 안전도를 높게 생각한다는 기사 확인
http://news1.kr/articles/?1911504 

분석 내용

1. 서울시 각 자치구 별 CCTV 수 파악
2. 인구 대비 CCTV 비율을 파악해 순위 비교
3. 인구 대비 CCTV의 평균치를 확인 후 CCTV가 가장 부족한 자치구 확인
4. 단순한 그래프 표현에서 한 단계 더 나아가 경향을 확인하고 시각화

 

 

 

2.  모듈 import

conda install -c anaconda xlrd

    모듈 import 전, 아나콘다프롬프트 혹은 spyder 콘솔에 상기 코드를 입력해 모듈이 원활하게 수행되도록 함.

 

#csv, excel 파일 읽기 모듈
import pandas as pd

#숫자 관련 모듈
import numpy as np

#시각화 작업 모듈
import matplotlib.pyplot as plt
from matplotlib import font_manager, rc

#사용 컴퓨터의 운영체제 확인 모듈
import platform

#한글 폰트 손상 방지
plt.rcParams['axes.unicode_minus'] = False

#운영체제에 맞는 기본 폰트 설정
if platform.system() == 'Darwin':     #애플
    rc('font', family='AppleGothic')
elif platform.system() == 'Windows':
    path = 'c:/Windows/Fonts/malgun.ttf'
    font_name = font_manager.FontProperties(fname=path).get_name()
    rc('font', family=font_name)
else:
    print('sorry!')

 

 

 

3.  데이터 읽기

3-1. 서울시 cctv 데이터 읽기 및 정제

1) 파일 읽기 :    pandas.read_csv('파일경로')

CCTV_seoul = pd.read_csv('./cctv/data1/01. CCTV_in_Seoul.csv', encoding='utf-8')

 

2) 열 이름 확인 :    DataFrame.columns / DataFrame.columns[index열]

CCTV_seoul.columns
CCTV_seoul.columns[0]
CCTV_seoul.head()

 

3) index 열 이름 변경 :    DataFrame.rename(columns={DataFrame.columns[index열] : '변경할 이름'}, inplace=True)

CCTV_seoul.rename(columns={CCTV_seoul.columns[0] : '자치구'}, inplace=True)

#변경 여부 확인
CCTV_seoul.columns[0]

    '기관명'이라고 되어 있는 index 열을 '자치구'로 변경,

    inplace를 True로 적용해야 변경사항을 원본 DataFrame에 적용.

 

 

3-2. 서울시 인구 데이터  읽기 및 정제

1) 파일 읽기 :    pandas.read_excel('파일경로')

#전체 읽어 올 경우
pop_Seoul = pd.read_excel('./cctv/data1/01. population_in_Seoul.xls')

#분석에 유의미한 부분만 선택해 읽어 올 경우
#header=엑셀 행(인덱스) 번호 : 엑셀의 첫 몇 줄로 불필요하면 입력한 숫자만큼 건너뜀
#usecols='엑셀 열 이름, 이름....'  /  B:구이름, D:인구수, G:한국인, J:외국인, N:고령자(65세이상)
pop_Seoul = pd.read_excel('./cctv/data1/01. population_in_Seoul.xls',
                          header=2,
                          usecols='B, D, G, J, N')

pop_Seoul.head()

#결과
'''
   자치구           계        계.1       계.2   65세이상고령자
0   합계  10197604.0  9926968.0  270636.0  1321458.0
1  종로구    162820.0   153589.0    9231.0    25425.0
2   중구    133240.0   124312.0    8928.0    20764.0
3  용산구    244203.0   229456.0   14747.0    36231.0
4  성동구    311244.0   303380.0    7864.0    39997.0
'''

 

2) 각 열 이름 변경

#자치구 : 구별, 계 : 인구수, 계1 : 한국인, 계2 : 외국인, 65세이상고령자 : 고령자

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.columns

#결과
'''
Index(['자치구', '인구수', '한국인', '외국인', '고령자'], dtype='object')
'''

 

 

 

4.  데이터 전처리

4-1.  서울시 cctv 데이터 파악

    13년도 이전과 이후 CCTV 증가율을 나타내는 '최근증가율' 열 추가.

CCTV_seoul.head(5)
CCTV_seoul.tail(5)

#DataFrame에 열 추가 : DataFrame['새 열 이름'] = 추가 예정 데이터
#증가율 = (2014년 + 2015년 + 2016년) / 2013년 이전 * 100
CCTV_seoul['최근증가율'] = (CCTV_seoul['2014년']+CCTV_seoul['2015년']+CCTV_seoul['2016년'])/
						 CCTV_seoul['2013년도 이전'] * 100

#결과
'''
0     150.619195
1     166.490765
2     125.203252
3     134.793814
4     149.290780
5      53.228621
6      64.973730
7     100.000000
8     188.929889
9     246.638655
10     74.766355
11    139.338235
12    212.101911
13     48.578199
14     63.371266
15     81.780822
16     63.627354
17    104.347826
18     34.671731
19    157.979798
20     53.216374
21     85.237258
22    248.922414
23    147.699758
24     79.960707
Name: 최근증가율, dtype: float64
'''



CCTV_seoul.sort_values(by='최근증가율', ascending=False).head()

#결과
'''
    자치구    소계  2013년도 이전  2014년  2015년  2016년       최근증가율
22  종로구  1002        464    314    211    630        248.922414
9   도봉구   485        238    159     42    386        246.638655
12  마포구   574        314    118    169    379        212.101911
8   노원구  1265        542     57    451    516        188.929889
1   강동구   773        379     99    155    377        166.490765
'''

 

 

4-2. 서울시 인구 데이터 파악

1) 결측치 확인 및 처리

#결측치 NaN 확인 : isnull()
#유일 값 확인 : unique()
#행 삭제 : drop([행index번호])

#'합계' 행 삭제 : 전체 자치구 값의 합계이므로 불필요
pop_Seoul.drop([0], inplace=True)



#'자치구' 열의 유일 값 확인
pop_Seoul['구별'].unique()

#결과
'''
array(['종로구', '중구', '용산구', '성동구', '광진구', '동대문구', '중랑구', '성북구', '강북구',
       '도봉구', '노원구', '은평구', '서대문구', '마포구', '양천구', '강서구', '구로구', '금천구',
       '영등포구', '동작구', '관악구', '서초구', '강남구', '송파구', '강동구', nan],
      dtype=object)
'''



#'자치구' 열의 결측치 확인
pop_Seoul[pop_Seoul['구별'].isnull()]

#결과
'''
    자치구  인구수  한국인  외국인  고령자
26  NaN  NaN  NaN  NaN  NaN
'''



#결측치 행 삭제 _ 해당 행에서의 결측은 데이터에 크게 영향을 미치지 않음
pop_Seoul.drop([26], inplace=True)
pop_Seoul.tail()

#결과
'''
    자치구       인구수       한국인      외국인      고령자
21  관악구  525515.0  507203.0  18312.0  68082.0
22  서초구  450310.0  445994.0   4316.0  51733.0
23  강남구  570500.0  565550.0   4950.0  63167.0
24  송파구  667483.0  660584.0   6899.0  72506.0
25  강동구  453233.0  449019.0   4214.0  54622.0
'''

 

2) 외국인과 고령자 비율 열 생성

    외국인과 고령자의 비율을 별도의 열을 생성하여 파악.

#'외국인비율' = '외국인' / '인구수' * 100
pop_Seoul['외국인비율'] = pop_Seoul['외국인'] / pop_Seoul['인구수'] * 100

#'고령자비율' = '고령자' / '인구수' * 100
pop_Seoul['고령자비율'] = pop_Seoul['고령자'] / pop_Seoul['인구수'] * 100

#인구수/외국인/외국인비율/고령자/고령자비율을 기준으로 각각 정렬 후 데이터 확인
pop_Seoul.sort_values(by='인구수', ascending=False).head(5)
pop_Seoul.sort_values(by='외국인', ascending=False).head(5)
pop_Seoul.sort_values(by='외국인비율', ascending=False).head(5)
pop_Seoul.sort_values(by='고령자', ascending=False).head(5)
pop_Seoul.sort_values(by='고령자비율', ascending=False).head(5)

#결과
'''
     자치구       인구수       한국인      외국인      고령자     외국인비율      고령자비율
9    강북구  330192.0  326686.0      3506.0     54813.0    1.061806  16.600342
1    종로구  162820.0  153589.0      9231.0     25425.0    5.669451  15.615404
2     중구  133240.0  124312.0      8928.0     20764.0    6.700690  15.583909
3    용산구  244203.0  229456.0     14747.0     36231.0    6.038828  14.836427
13  서대문구  327163.0  314982.0     12181.0     48161.0    3.723221  14.720797
'''

 

 

4-3. CCTV와 인구 데이터 병합 및 정제

1) CCTV와 인구 데이터 병합

    새DataFrame = Pandas.merge(DataFrame1, DataFrame2, on='공통 열')

#Pandas.merge(CCTV_seoul, pop_Seoul, on='공통 열')
#만약 두 프레임의 공통 열이 없다면 : left_on='열', right_on='열'
data_result = pd.merge(CCTV_seoul, pop_Seoul, on='구별')
data_result.head()
data_result.tail()

 

2) 불필요한 열 삭제 :    del DataFrame['열']

#2013년도 이전 / 2014년 / 2015년 / 2016년 : 현재에 해당하는 '소계'만 남기고 삭제
#자치구 / 소계 / 최근증가율 / 인구수 / 한국인 / 외국인 / 고령자 / 외국인비율
del data_result['2013년도 이전']
del data_result['2014년']
del data_result['2015년']
del data_result['2016년']
data_result.head()

#결과
'''
   자치구    소계       최근증가율       인구수  ...      외국인      고령자     외국인비율      고령자비율
0  강남구  2780    150.619195  570500.0  ...       4950.0  63167.0    0.867660  11.072217
1  강동구   773    166.490765  453233.0  ...       4214.0  54622.0    0.929765  12.051638
2  강북구   748    125.203252  330192.0  ...       3506.0  54813.0    1.061806  16.600342
3  강서구   884    134.793814  603772.0  ...       6524.0  72548.0    1.080540  12.015794
4  관악구  1496    149.290780  525515.0  ...      18312.0  68082.0    3.484582  12.955291

[5 rows x 9 columns]
'''

 

3) 분석 및 시각화를 위해 '자치구' 열을 index로 설정

    DataFrame.set_index('index로 사용될 열', inplace=True)

data_result.set_index('자치구', inplace=True)

 

 

 

5.   분석 및 시각화

5-1. 각 데이터 간 연관성 파악을 위해 상관계수 산출 :    Numpy.corrcoef()

#고령자비율과 소계 간 상관 관계
np.corrcoef(data_result['고령자비율'], data_result['소계'])

#결과
'''
array([[ 1.        , -0.28078554],
       [-0.28078554,  1.        ]])
'''



#인구수와 소계 간 상관 관계
np.corrcoef(data_result['인구수'], data_result['소계'])

#결과
'''
array([[1.        , 0.30634228],
       [0.30634228, 1.        ]])
'''

 

 

5-2. 시각화

1) 자치구 및 소계 데이터 시각화

plt.figure()
data_result['소계'].plot(kind='barh',
                       grid=True,
                       figsize=(10, 10))
plt.show()

 

2) 소계를 기준으로 정렬 후 시각화

data_result['소계'].sort_values().plot(kind='barh',
                                     grid=True,
                                     figsize=(10, 10))
plt.show()

 

3) 인구수 대비 CCTV비율 시각화

data_result['CCTV비율'] = data_result['소계'] / data_result['인구수'] * 100

data_result['CCTV비율'].sort_values().plot(kind='barh',
                                         grid=True,
                                         figsize=(10, 10))
plt.show()

 

4) 인구수와 소계에 대한 산점도

plt.figure(figsize=(6, 6))
plt.scatter(data_result['인구수'], data_result['소계'], s=50)  #s:산점도 점 크기
plt.xlabel('인구수')
plt.ylabel('cctv')
plt.grid()
plt.show()

 

5) 인구수와 CCTV비율에 대한 산점도

plt.figure(figsize=(6, 6))
plt.scatter(data_result['인구수'], data_result['CCTV비율'], s=50)
plt.xlabel('인구수')
plt.ylabel('cctv')
plt.grid()
plt.show()

 

6) 인구수와 CCTV비율에 대한 산점도의 선형회귀선    *선형회귀 : 두 변수(열)의 관계

fp1 = np.polyfit(data_result['인구수'], data_result['소계'], 1)
f1 = np.poly1d(fp1)     #직선 만들기
fx = np.linspace(100000, 700000, 100)     #(부터, 까지, 간격)으로 그리기

plt.figure(figsize=(10,10))
plt.scatter(data_result['인구수'], data_result['소계'], s=50)
plt.plot(fx, f1(fx), ls='dashed', lw=3, color='r')    #ls : line style, lw : line width
plt.xlabel('인구수')
plt.ylabel('cctv')
plt.grid()
plt.show()

 

 

 

6. 결론

    서울시 내 강남구, 양천구, 용산구, 서초구, 은평구는 인구 대비 cctv가 많고,
    강서구, 송파구, 도봉구 등은 인구 대비 부족함. 따라서 강남 3구 전체가 안전하다고 판단하기는 어려움.

 

 


7. 시각화 심화하기

7-1. '오차' 산출 :     data_result['소계'] - data_result['인구수']

        절댓값 산출 :    Numpy.abs()

fp1 = np.polyfit(data_result['인구수'], data_result['소계'], 1)
f1 = np.poly1d(fp1)
fx = np.linspace(100000, 700000, 100)

data_result['오차'] = np.abs(data_result['소계'] - f1(data_result['인구수']))

df_sort = data_result.sort_values(by='오차', ascending=False)

 

 

7-2. 시각화

plt.figure(figsize=(14, 10))
plt.scatter(df_sort['인구수'], df_sort['소계'],
            c=data_result['오차'],
            s=50)
plt.plot(fx, f1(fx), ls='dashed', lw=3, color='r')

for n in range(10):
    plt.text(df_sort['인구수'][n]*1.02,     #x좌표
             df_sort['소계'][n]*0.98,     #y좌표
             df_sort.index[n],     #자치구
             fontsize=15)

plt.xlabel('인구수')
plt.ylabel('인구당 비율')
plt.colorbar()
plt.grid()
plt.show()