왜 정치 데이터 분석에 Google Colab인가?
라이브러리 충돌 문제를 원천 차단합니다.
해결 프로세스 (순서 엄수):
폰트 설치 코드 실행:
!sudo apt-get install -y fonts-nanum
!sudo fc-cache -fv
!rm ~/.cache/matplotlib -rf
데이터 분석의 '도구 상자'를 여는 과정입니다. 표준 약칭(Alias)을 사용하여 가독성을 높입니다.
표준 관례(Naming Convention):
import pandas as pd # 데이터 프레임 핸들링의 핵심 (Panels Data)
import numpy as np # 고성능 수치 계산 및 결측치(NaN) 처리
import matplotlib.pyplot as plt # 정적 그래프 시각화
import seaborn as sns # 통계 기반의 미려한 시각화 도구
왜 as pd인가? 코드 작성 효율을 높일 뿐만 아니라, 전 세계 데이터 과학자들이 공유하는 일종의 '언어'입니다. pd라는 접두사만 보고도 이 함수가 Pandas의 기능임을 즉각 인지할 수 있게 합니다.
클라우드 가상 컴퓨터에 내 구글 드라이브 폴더를 '하드디스크'처럼 연결하는 과정입니다.
연결 절차:
from google.colab import drive
drive.mount('/content/drive')
기능을 활용하면 오타를 줄일 수 있습니다.
"Pandas는 데이터를 다루는 파이썬의 엑셀입니다."
단순한 리스트와 달리 '의미 있는 이름'을 가진 인덱스를 가집니다.
정치 데이터 생성 예시:
# 후보별 지지율을 시리즈로 표현
polls = pd.Series([42.5, 38.1, 12.4],
index=['후보A', '후보B', '후보C'],
name='지지율')
print(polls)
현실 세계의 복잡한 정치적 사건들은 여러 변수의 조합으로 설명됩니다. DataFrame은 이를 담기에 최적의 그릇입니다.
구성 요소 및 특징:
정형 데이터 분석의 흐름은 "외부 데이터를 읽어와 DataFrame으로 만들고, 이를 가공하여 결론을 내는 과정"으로 정의됩니다.
우리는 가상의 혹은 실제 공공데이터인 **"제21대 국회의원 선거 결과 요약.csv"**를 분석합니다.
주요 변수(Variable) 설명:
이 데이터를 통해 지역별 정당 지지 강도와 투표율 사이의 상관관계를 탐색할 예정입니다.
텍스트 형태의 CSV 데이터를 메모리 상의 DataFrame 객체로 전환합니다.
실전 코드와 파라미터:
file_path = '/content/drive/MyDrive/data/election_21st.csv'
df = pd.read_csv(file_path, encoding='cp949', sep=',')
인코딩(Encoding) 트러블슈팅:
수만 건의 선거 데이터를 한꺼번에 출력하면 가독성이 떨어지고 시스템 부하가 생깁니다.
주요 기능:
df.head(n): 상위 n개의 행을 보여줍니다. 데이터의 헤더(컬럼명)가 제대로 들어왔는지, 데이터가 밀리지 않았는지 확인합니다.
df.tail(n): 하위 n개의 행을 보여줍니다. 데이터의 끝부분에 합계나 불필요한 주석(Footnote)이 포함되어 데이터 오염을 일으키지 않는지 체크합니다.
데이터의 외형적 스펙을 수치로 확인하는 단계입니다.
출력 결과: Index(['sgg_name', 'party', ...], dtype='object')
각 변수의 성격과 데이터의 결함 여부를 파악하는 가장 강력한 요약 도구입니다.
체크리스트:
기술 통계(Descriptive Statistics)의 집약체입니다. 데이터의 중심과 퍼짐 정도를 요약합니다.
주요 지표의 정치적 해석:
확장: df.describe(include='all')을 쓰면 문자열 데이터의 고유값 개수(unique), 최빈값(top) 등 범주형 데이터의 특성도 한꺼번에 파악할 수 있습니다.
"원하는 데이터만 정확하게 골라내는 것이 분석의 절반입니다."
정치 데이터는 보통 수천 개의 행(Row)으로 구성됩니다. 모든 데이터를 한꺼번에 분석하는 것은 노이즈가 많아 비효율적입니다. 따라서 특정 정당, 특정 지역, 혹은 특정 득표 구간의 데이터만 정밀하게 추출하는 과정이
분석의 성패를 결정합니다.
주요 도구 및 전략:
로우 데이터(Raw Data)에는 분석에 필요 없는 부가 정보가 많습니다. 필요한 변수만 떼어내어 새로운 분석 단위를 만듭니다.
단일 열 선택 (Series 반환):
# 한 개의 열만 가져오면 인덱스와 값만 남은 Series 형태가 됩니다.
party_list = df['party']
여러 열 선택 (DataFrame 반환):
# 여러 열을 선택할 때는 대괄호 안에 '리스트'를 넣어야 하므로 대괄호가 두 번([[ ]]) 쓰입니다.
subset = df[['sgg_name', 'candidate', 'votes']]
속도와 분석의 선명도를 동시에 잡을 수 있습니다.
파이썬의 기본 리스트 슬라이싱과 유사하게 행의 범위를 지정하여 데이터를 잘라냅니다.
코드 실습:
# 0번부터 9번 행까지 총 10개의 사례를 출력합니다.
top_10 = df[0:10]
# 마지막 5개 행만 확인하여 데이터 끝부분의 무결성을 검사합니다.
bottom_5 = df[-5:]
.loc나 .iloc를 사용하는 습관을 들이는 것이 좋습니다.
실습 예시 (특정 선거구 데이터 추적):
# 인덱스가 '선거구명'으로 설정되어 있는 경우, 해당 선거구의 모든 변수를 가져옵니다.
jongno_data = df.loc['서울 종로구', :]
슬라이싱 응용 및 유의점:
# '서울'부터 '부산'까지의 모든 데이터, '정당'부터 '득표수' 컬럼까지 추출
regional_votes = df.loc['서울':'부산', 'party':'votes']
심화 사례:
# 가장 첫 번째 행(0)의 세 번째 열(2) 데이터를 즉시 확인합니다.
first_val = df.iloc[0, 2]
# 전체 데이터 중 뒤에서 10번째부터 끝까지, 처음 2개의 변수만 선택
recent_subset = df.iloc[-10:, :2]
논리적 단계:
마스크 생성: mask = df['votes'] > 50000 (각 행이 조건에 맞는지 검사하여 True/False 시리즈 생성)
필터 적용: filtered_df = df[mask] (True인 행만 추출)
정치적 인사이트 도출 예시:
정치적 상황은 단일 변수로 설명되지 않습니다. 여러 조건을 조합하여 정교한 분석 대상을 설정합니다.
# 'A정당' 소속이면서 동시에 '득표율 15%'를 넘겨 선거 보전금을 받는 후보들
cond1 = df['party'] == 'A정당'
cond2 = df['vote_rate'] >= 15
result_and = df[cond1 & cond2]
# 수도권 정치 지형 분석을 위해 '서울' 또는 '경기' 지역 데이터만 통합
result_or = df[(df['city'] == '서울') | (df['city'] == '경기')]
여러 개의 OR 조건을 반복해서 쓰는 번거로움과 코드의 난잡함을 획기적으로 줄여줍니다.
코드 개선:
# 비효율적 방식: (df['party'] == 'A') | (df['party'] == 'B') | ...
# 효율적 방식: .isin() 활용
major_parties = ['더불어민주당', '국민의힘', '정의당']
df_major = df[df['party'].isin(major_parties)]
수치 데이터가 아닌 텍스트 데이터 내에서 특정 패턴이나 키워드를 찾아냅니다.
정치 데이터 실무 활용:
코드 실습:
# 선거구 이름에 '강남'이라는 텍스트가 포함된 모든 행을 추출합니다.
gangnam_districts = df[df['sgg_name'].str.contains('강남')]
수집 과정에서 누락된 데이터는 분석 결과에 심각한 왜곡을 줄 수 있습니다. 이를 찾아내거나 제외하는 작업입니다.
도구 설명:
정치적 의미 및 적용:
여론조사 로우 데이터에서 '지지 후보 없음' 혹은 '모름/무응답'으로 처리된 결측치를 제외하고, 실제 투표 의사가 있는 유효 응답자(Valid Respondents)만을 분석 모수로 확정할 때 사용합니다.
# 득표수 정보가 정상적으로 기입된 행만 남기고 분석을 진행합니다.
df_clean = df[df['votes'].notnull()]
데이터를 필터링하거나 정렬하면 기존의 행 번호(0, 1, 5, 12...)가 그대로 유지됩니다. 이는 데이터의 이빨이 빠진 것과 같은 상태입니다.
발생할 수 있는 문제:
이후 분석에서 행 번호를 기준으로 반복문을 돌릴 때 에러가 발생합니다.
시각화 도구에서 데이터 포인트가 엉뚱한 위치에 찍힐 수 있습니다.
해결 방법:
# 필터링 직후 인덱스를 0부터 다시 순차적으로 부여합니다.
# drop=True를 생략하면 기존의 지저분한 인덱스가 새로운 컬럼으로 남아 데이터셋이 지저분해집니다.
df_filtered = df[df['votes'] > 10000].reset_index(drop=True)
분석 결과를 시각화하거나 리포트를 작성할 때, 데이터를 논리적인 순서로 나열하여 설득력을 높입니다.
단일 기준 정렬:
# 득표수(votes)를 기준으로 가장 많은 후보부터 적은 후보 순(내림차순)으로 나열
df_sorted = df.sort_values(by='votes', ascending=False)
다중 기준 정렬 (계층적 정렬):
정치 분석에서는 "지역별로 먼저 묶고(가나다순), 그 안에서 득표율 순(내림차순)으로 보고 싶다"는 요구가 많습니다.
# city는 오름차순(True), votes는 내림차순(False)으로 각각 다른 기준 적용
df_multi_sorted = df.sort_values(by=['city', 'votes'],
ascending=[True, False])
범주형(Category) 데이터의 빈도수를 계산하여 데이터의 전반적인 구성을 파악합니다.
분석 질문:
"이번 총선에서 가장 많은 후보자를 낸 정당은 어디인가?"
"선거구별로 출마한 후보자의 수는 보통 몇 명인가?"
실전 코드:
# 정당별 후보자 수를 카운트합니다.
party_counts = df['party'].value_counts()
# 숫자가 아닌 비율(%)로 보고 싶을 때
party_ratio = df['party'].value_counts(normalize=True) * 100
중복을 제거한 실제 데이터의 종류와 그 개수를 확인합니다.
분석 시나리오:
분석 조건:
작업 순서 가이드:
df['diff'] = ... 를 통해 1, 2위 간 득표 차이 파생 변수를 만듭니다. (다음 장에서 상세 학습)
& 연산자로 투표율 조건과 득표 차 조건을 결합합니다.
sort_values와 reset_index를 체이닝(Chaining)하여 결과를 완성합니다.
"쓰레기를 넣으면 쓰레기가 나온다 (Garbage In, Garbage Out)."
데이터 분석의 80%는 전처리에 소요됩니다. 수집된 정치 데이터는 절대 분석에 최적화된 상태가 아닙니다. 오타가 있을 수도 있고, 숫자가 문자로 인식되어 연산이 불가능할 수도 있으며, 날짜 형식이 파편화되어 시계열
분석을 방해하기도 합니다.
이번 섹션의 핵심 목표:
전처리를 시작하기 전, 우리는 데이터의 '기저 질환'을 파악해야 합니다. 단순한 조회를 넘어 데이터의 무결성을 최종 점검하는 단계입니다.
정밀 진단 체크리스트:
실전 분석 팁: df.describe(include='all')은 분석가의 가장 강력한 무기입니다. 숫자 데이터의 분포뿐만 아니라, 문자열 데이터에서 가장 자주 등장하는 정당(top)이 어디인지, 정당의
종류(unique)가 몇 개인지 등을 통해 데이터 수집의 편향성을 즉각 포착할 수 있습니다.
공공데이터 포털 등에서 다운로드한 CSV 파일은 숫자를 따옴표로 감싸거나 천 단위 구분 기호(,)를 포함하여 문자열로 인식되는 경우가 허다합니다.
타입 변환이 생존인 이유:
# 문자열 상태('100')에서는 더하기(+)를 수행하면 산술 합이 아닌 문자열 이어붙이기가 됩니다.
# 예: '100' + '200' 결과는 300이 아닌 '100200'이라는 엉뚱한 문자열이 됩니다.
# 따라서 반드시 분석 전에 타입 교정이 필요합니다.
df['votes'] = df['votes'].astype(int)
변환하거나, 결측치를 허용하는 float 타입으로 변환한 뒤 후속 조치를 취해야 합니다.
단순한 형변환(astype)이 실패하는 가장 큰 이유는 숫자 사이에 섞인 '불순물' 때문입니다. 콤마(,)나 '표', '명'과 같은 단위가 대표적입니다.
현장 기반 해결 시나리오 (득표수 정밀 정제):
# 정규표현식을 사용하거나 단순 replace를 활용하여 콤마를 제거합니다.
# 이후 바로 astype(int)를 호출하여 정수형 데이터프레임 열로 확정합니다.
df['votes'] = df['votes'].str.replace(',', '').astype(int)
과정입니다.
비어있는 데이터(NaN)를 어떻게 다루느냐는 분석 결과의 신뢰도를 결정짓는 윤리적 문제이기도 합니다.
특정 후보자가 사퇴하거나 등록 무효가 되어 데이터의 연속성이 깨진 경우, 해당 행을 분석 대상에서 제외하는 것이 통계적 오염을 막는 길입니다.
# '후보자명'이 비어있는 데이터는 유령 데이터로 간주하여 행 전체를 삭제합니다.
df_final = df.dropna(subset=['candidate'])
여론조사에서 '무응답'이 발생한 경우, 이를 단순히 삭제하기보다 0으로 처리하거나 전체 평균값(mean), 혹은 중앙값(median)으로 대체하여 모수를 유지합니다.
# 지지율 미응답 사례를 0%로 간주하여 계산의 일관성을 확보합니다.
df['support_rate'] = df['support_rate'].fillna(0)
크롤링이나 여러 파일의 병합 과정에서 동일한 선거 결과가 중복 입력되는 경우가 빈번합니다.
실습과 응용:
# 선거구와 후보자 이름이라는 두 가지 기준이 모두 일치하는 중복을 찾아 제거합니다.
# keep='last' 옵션은 시간순으로 수집된 데이터 중 가장 최신의 정보를 남기겠다는 선언입니다.
df_unique = df.drop_duplicates(subset=['sgg_name', 'candidate'], keep='last')
위험이 있습니다.
원본 데이터에는 없지만, 분석가의 가설을 검증하기 위해 기존 변수들을 수학적으로 조합해 만든 새로운 지표입니다.
정치 데이터의 핵심 파생 변수:
df['vote_rate'] = (df['votes'] / df['total_votes']) * 100
단순 사칙연산을 넘어 "특정 조건에 따른 라벨링"을 수행합니다. 엑셀의 IF 함수와 동일한 역할을 수행합니다.
import numpy as np
# 득표율 50%를 기준으로 '과반 당선(Majority)'과 '기타(Others)'로 후보군을 이분화합니다.
# 이 과정은 향후 정당별 과반 의석 확보 가능성을 시뮬레이션할 때 기초가 됩니다.
df['result_type'] = np.where(df['vote_rate'] >= 50, 'Majority', 'Others')
"더불어민주당", "민주당 ", "더민주" 등 제각각인 정당 명칭은 데이터 그룹화 시 서로 다른 그룹으로 인식되는 대참사를 일으킵니다.
# .str.strip() 메서드를 통해 모든 텍스트 열의 앞뒤 공백을 일괄 제거합니다.
df['party'] = df['party'].str.strip()
명칭 통합의 기술:
# 특정 단어를 포함(contains)하는 모든 행을 찾아 표준 명칭으로 강제 통일합니다.
# 이를 통해 파편화된 정당 명칭을 하나로 결속시킵니다.
df.loc[df['party'].str.contains('민주당'), 'party'] = '더불어민주당'
정해진 규칙(매핑 테이블)에 따라 데이터의 값을 대규모로 치환할 때 사용합니다.
딕셔너리 기반 매핑:
# 긴 행정구역 명칭을 분석 보고서용 약어로 일괄 변환합니다.
# {기존값 : 변경값} 형태의 딕셔너리를 작성하여 .map()에 전달합니다.
df['city_short'] = df['city'].map(region_map)
선거일, 후보 등록일, 여론조사 공표일 등은 단순 문자열이 아닌 '시간'으로 인식되어야 그 선후 관계와 간격을 분석할 수 있습니다.
날짜 객체로의 진화:
# 문자열 형태의 '2024-04-10'을 파이썬이 이해하는 날짜 객체(Datetime)로 바꿉니다.
df['election_date'] = pd.to_datetime(df['election_date'])
# 날짜 객체에서 '월'만 추출하거나 '요일'을 알아내는 연산이 가능해집니다.
df['month'] = df['election_date'].dt.month
df['weekday_name'] = df['election_date'].dt.day_name()
원본 데이터의 난해한 컬럼명을 분석 목적에 맞는 명확한 한글이나 영문으로 변경합니다.
방법론:
# columns 인자에 {기존컬럼명 : 신규컬럼명} 딕셔너리를 전달합니다.
# 분석 협업 시 팀원들이 데이터의 의미를 즉각 파악하도록 돕는 배려이기도 합니다.
df = df.rename(columns={'sgg_name': '선거구', 'votes': '득표수'})
할당(Re-assignment)해야 변경 사항이 영구히 저장됩니다.
시군구별로 흩어져 있는 수십 개의 엑셀 파일을 하나의 거대한 데이터셋으로 통합하는 과정입니다.
결합 시나리오:
seoul_df와 busan_df가 동일한 컬럼 구조(변수명)를 가지고 있을 때, 이를 위아래로 길게 이어 붙입니다.
# axis=0 옵션은 수직 결합(위아래)을 의미합니다.
# .reset_index(drop=True)를 호출하여 섞여버린 행 번호를 0부터 다시 정렬하는 것이 필수입니다.
total_df = pd.concat([seoul_df, busan_df], axis=0).reset_index(drop=True)
지금까지 개별적으로 학습한 전처리 도구들을 실제 분석에서는 하나의 유기적인 흐름으로 묶어 실행해야 합니다.
분석가가 지켜야 할 정석 순서:
상세 요구사항 (Step-by-Step):
"숫자 뒤에 숨겨진 정치적 흐름을 읽는 법"
전처리가 끝난 데이터는 이제 깨끗한 상태입니다. 하지만 수만 행의 로우 데이터를 단순히 눈으로 훑는 것만으로는 거시적인 정치적 흐름을 파악할 수 없습니다. 수치화된 요약은 복잡한 현상을 단순화하여 의사결정을 돕는
강력한 도구입니다.
이번 섹션의 상세 목표:
groupby는 단순히 데이터를 묶는 것이 아니라, **Split(분할) - Apply(적용) - Combine(결합)**이라는 정교한 내부 논리를 거칩니다.
party_stats = df.groupby('party')['vote_rate'].mean()
# 정당별 득표율의 평균, 합계, 그리고 출마 후보자 수(count)를 한눈에 비교합니다.
multi_stats = df.groupby('party')['vote_rate'].agg(['mean', 'sum', 'count'])
"서울에서의 정당별 득표율과 경기에서의 득표율은 어떻게 다른가?"와 같은 복합적인 정치 지형을 분석할 때 사용합니다.
실전 코드:
# 시도(city)와 정당(party) 두 가지 변수를 리스트로 묶어 기준(index)으로 설정합니다.
# 그 결과로 각 지역 내에서의 정당별 총 득표수(votes)를 집계합니다.
city_party_grp = df.groupby(['city', 'party'])['votes'].sum()
수치로 입증할 수 있습니다. 결과물은 보통 계층적 구조(Multi-Index)를 가지게 됩니다.
groupby보다 시각적으로 더 직관적이고 보고서에 적합한 구조를 만들 때 사용합니다. 엑셀 사용자들이 가장 선호하는 Pandas 기능 중 하나입니다.
구조 설계의 4요소:
# 지역을 행으로, 정당을 열로 두어 득표수 합계를 한눈에 보여주는 표를 만듭니다.
pivot = df.pivot_table(index='city', columns='party', values='votes', aggfunc='sum')
수만 명의 후보자나 수천 개의 선거구를 단 하나의 숫자로 대표할 수 있을까요? 상황에 맞는 '중심'을 찾는 것이 중요합니다.
주요 지표와 정치적 해석:
"우리 당 지지율이 평균 30%다"라는 말보다 "지역별로 편차 없이 고르게 30%다"라는 정보가 선거 전략상 훨씬 중요할 수 있습니다.
산포 지표의 활용:
기술 통계 숫자를 보고 머릿속으로 그리던 분포를 실제로 확인하는 단계입니다.
도구와 해석:
# Pandas의 내장 기능을 활용해 빠른 시각화를 수행합니다.
# 구간(bins)을 20개로 쪼개어 세밀한 분포를 확인합니다.
df['vote_rate'].hist(bins=20, figsize=(10, 6))
"두 변수가 함께 움직이는가?"라는 질문에 답합니다. 정치적 가설을 검증하는 첫걸음입니다.
상관계수 (Pearson's r)의 해석:
있음을 항상 의심해야 합니다.
Colab 세션이 종료되면 업로드한 파일은 사라집니다. 이를 방지하기 위해 구글 드라이브를 하드디스크처럼 연결합니다.
연동 프로세스:
왼쪽 사이드바의 '파일' 아이콘 클릭 후 '드라이브 마운트' 버튼을 누르거나, 아래 코드를 실행합니다.
팝업창에서 계정 권한을 허용합니다.
이제 MyDrive 폴더 내의 모든 파일을 경로만으로 불러올 수 있습니다.
from google.colab import drive
drive.mount('/content/drive')
# 실제 파일 경로 예시: 내 드라이브의 'data' 폴더 안의 파일
path = '/content/drive/MyDrive/data/election_results_2024.csv'
df = pd.read_csv(path)
Colab은 리눅스 기반 서버이므로 기본적으로 나눔 폰트 같은 한글 글꼴이 없습니다. 시각화 시 한글이 깨지는 현상은 분석가를 당황하게 만듭니다.
해결 시퀀스:
나눔 폰트를 서버에 설치합니다. (!apt-get install)
Matplotlib의 캐시를 새로고침하고 폰트를 지정합니다.
**반드시 '런타임 다시 시작'**을 해야 설정이 적용되는 경우가 많습니다.
# 폰트 설치 코드
!apt-get install -y fonts-nanum
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
# 설치된 나눔바른고딕 폰트 지정
plt.rc('font', family='NanumBarunGothic')
힘들게 전처리하고 분석한 결과물은 파일로 내보내야 동료에게 전달하거나 보고서에 삽입할 수 있습니다.
실무 표준 코드:
df_summary.to_csv('result_summary.csv', index=False, encoding='utf-8-sig')
df_summary.to_excel('analysis_report.xlsx', sheet_name='2024_Results')
있습니다.
%load_ext google.colab.data_table
df # 이제 이 표는 동적으로 조작 가능합니다.
완성된 워크플로우:
우리는 지난 2주 동안 파이썬의 기초 문법부터 Pandas를 활용한 정교한 데이터 조작, 그리고 통계적 요약까지 숨 가쁘게 달려왔습니다.
가슴에 새길 분석가 3계명:
오늘의 주요 토론 주제:
"결측치를 평균으로 채웠을 때 발생할 수 있는 정치적 편향은 무엇인가?"
"피벗 테이블의 구조를 바꿀 때(T) 가장 주의해야 할 점은?"
드디어 숫자를 그림으로 바꿉니다! 오늘 만든 통계 테이블을 바탕으로, 유권자의 마음을 한눈에 사로잡는 화려한 차트와 지도를 제작합니다.