전국 학원 교습소 정보 데이터셋은 대한민국 전역의 학원 및 교습소의 기초 정보(명칭, 주소, 교습분야, 정원 등)를 담고 있는 실제 행정 데이터입니다. 이 데이터를 활용해 데이터 선택(loc, iloc), Boolean Indexing, pd.col(), query(), 그리고 MultiIndex의 활용법을 차례로 실습합니다.
11.1 데이터 준비 및 탐색
먼저 필요한 라이브러리를 임포트하고 한글 폰트를 설정한 후 데이터를 읽어옵니다. 이 데이터는 한국어 인코딩(CP949)으로 저장되어 있으므로 encoding="cp949"를 지정합니다. 또한 C++ 기반 초고속 파싱과 메모리 효율성을 위해 engine="pyarrow" 및 dtype_backend="pyarrow" 옵션을 활용해 PyArrow 백엔드로 데이터를 읽어옵니다.
import pandas as pdimport numpy as npimport platformimport matplotlib.pyplot as plt# 운영체제별 한글 폰트 설정if platform.system() =="Darwin": plt.rcParams["font.family"] ="Apple SD Gothic Neo"elif platform.system() =="Windows": plt.rcParams["font.family"] ="Malgun Gothic"else: plt.rcParams["font.family"] ="NanumGothic"plt.rcParams["axes.unicode_minus"] =False# 데이터 로드 (PyArrow 엔진 및 백엔드 적용)df = pd.read_csv("../data/학원교습소정보(20251231).csv", encoding="cp949", engine="pyarrow", dtype_backend="pyarrow",)# 데이터 크기 확인print("데이터 크기:", df.shape)# 데이터 컬럼과 상위 데이터 확인print("컬럼 목록:", df.columns.tolist())# PyArrow 데이터 타입 샘플 확인print("데이터 타입 샘플:\n", df.dtypes.head())
loc는 레이블(이름) 기반의 데이터 선택법이며, iloc는 정수 위치 기반의 데이터 선택법입니다. 데이터프레임의 기본 상태에서는 인덱스가 0부터 시작하는 연속된 정수이므로 loc와 iloc의 행 선택이 비슷하게 작동하는 것처럼 보일 수 있습니다. 하지만 둘 사이에는 근본적인 차이가 있습니다.
11.2.1 기본 위치 기반 선택 (iloc)
정수 인덱스를 사용하여 특정 위치의 행과 열을 가져옵니다.
# 첫 번째 행의 모든 열 가져오기first_row = df.iloc[0, :]print(first_row.head(10))# 처음 5개 행과 처음 3개 열 가져오기# iloc의 슬라이싱은 끝점(5)을 제외하여 0부터 4까지 5개 행을 반환합니다.subset_iloc = df.iloc[0:5, 0:3]print(subset_iloc)
시도교육청코드 B10
시도교육청명 서울특별시교육청
행정구역명 강남구
...
등록일자 19950804
등록상태명 개원
휴원시작일자
Name: 0, Length: 10, dtype: object
시도교육청코드 시도교육청명 행정구역명
0 B10 서울특별시교육청 강남구
1 B10 서울특별시교육청 강남구
2 B10 서울특별시교육청 강남구
3 B10 서울특별시교육청 강남구
4 B10 서울특별시교육청 강남구
11.2.2 이름 기반 선택 (loc)
데이터의 인덱스를 특정 의미 있는 열로 변경한 후, 레이블을 이용해 데이터를 선택해 봅니다. 여기서는 학원지정번호를 인덱스로 지정하여 실습해 봅니다.
# 학원지정번호를 인덱스로 설정df_indexed = df.set_index("학원지정번호")# 특정 학원지정번호를 가진 행의 학원명과 도로명주소 가져오기# loc의 행 선택자로 학원지정번호(정수 형태 레이블)를 지정합니다.academy_info = df_indexed.loc[2531, ["학원명", "도로명주소"]]print(academy_info)
학원명 도로명주소
학원지정번호
2531 (주)멘토르수학전문학원 서울특별시 강남구 남부순환로 2927
2531 유진컴퓨터학원 충청남도 천안시 서북구 두정중1길 6-9
11.2.3 loc와 iloc의 슬라이싱 차이 비교
loc와 iloc의 중요한 차이 중 하나는 슬라이싱 범위의 끝점 포함 여부입니다.
# iloc: 끝점 제외 (0부터 4까지 5개 행 선택)print("iloc 슬라이싱 개수:", len(df.iloc[0:5]))# loc: 끝점 포함 (인덱스 레이블 0부터 5까지 6개 행 선택)print("loc 슬라이싱 개수:", len(df.loc[0:5]))
iloc 슬라이싱 개수: 5
loc 슬라이싱 개수: 6
11.3 Boolean Indexing을 사용한 필터링
특정 조건을 만족하는 행들만 선택하는 강력한 도구입니다.
11.3.1 단일 조건 필터링
정원합계가 300명 이상인 대형 학원만 필터링합니다.
# 정원합계가 300명 이상인지 여부를 담은 Boolean Series 생성is_large = df["정원합계"] >=300# 필터링 적용 및 결과 행 수 확인large_academies = df[is_large]print("대형 학원 수:", large_academies.shape[0])
대형 학원 수: 13395
11.3.2 복합 조건 필터링
두 개 이상의 조건을 조합할 때는 AND(&), OR(|), NOT(~) 연산자를 사용합니다. 각 조건은 반드시 괄호로 감싸야 연산자 우선순위 오류를 방지할 수 있습니다. 행정구역명이 강남구이면서 분야명이 입시.검정 및 보습인 학원을 필터링합니다.
# 복합 조건 필터링gangnam_ipsi = df[ (df["행정구역명"] =="강남구")& (df["분야명"] =="입시.검정 및 보습")]print("강남구 입시 학원 수:", gangnam_ipsi.shape[0])print(gangnam_ipsi[["학원명", "교습과정명"]].head())
여러 개의 OR 조건을 연결해야 할 때 isin() 메서드를 사용하면 코드가 한결 간결해집니다. 행정구역이 강남 3구(강남구, 서초구, 송파구)인 학원을 필터링합니다.
gangnam_3gu = df[ df["행정구역명"].isin(["강남구", "서초구", "송파구"])]print("강남 3구 학원 수:", gangnam_3gu.shape[0])print(gangnam_3gu["행정구역명"].value_counts())
강남 3구 학원 수: 7230
행정구역명
강남구 3453
송파구 1945
서초구 1832
Name: count, dtype: int64[pyarrow]
11.4 pd.col()을 활용한 선언형 필터링
pandas 3.x부터 도입된 pd.col()은 함수형 프로그래밍 스타일의 선언적 코드를 지원합니다. 람다 식이나 복잡한 데이터프레임 참조 없이 컬럼을 쉽게 지칭할 수 있습니다.
# pd.col()을 사용하여 정원합계가 200명 이상이면서 일시수용능력인원합계가 150명 이상인 학원 필터링condition_col = (pd.col("정원합계") >=200) & ( pd.col("일시수용능력인원합계") >=150)filtered_col = df[condition_col]print("pd.col 필터링 결과 수:", filtered_col.shape[0])
pd.col 필터링 결과 수: 2886
11.5 query() 메서드를 활용한 데이터 조회
query() 메서드는 SQL의 WHERE 절처럼 문자열 형태로 직관적인 필터링 조건을 작성할 수 있게 해줍니다.
11.5.1 기본 query 사용법
# 행정구역명이 서초구이면서 정원합계가 100명 이상인 학원 조회query_result = df.query("행정구역명 == '서초구' and 정원합계 >= 100")print("서초구 100명 이상 학원 수:", query_result.shape[0])
서초구 100명 이상 학원 수: 666
11.5.2 외부 변수 참조 (@ 기호)
query() 문자열 내부에서 Python 프로그램의 외부 변수를 참조할 때는 변수명 앞에 @ 기호를 붙입니다.
# 외부 변수 정의limit_capacity =250target_area ="수원시"# query 안에서 외부 변수 참조suwon_large = df.query("행정구역명 == @target_area and 정원합계 >= @limit_capacity")print("수원시 대형 학원 수:", suwon_large.shape[0])
수원시 대형 학원 수: 595
11.5.3 문자열 검색 조건 활용
query() 내에서 문자열 메서드인 str.contains() 등을 직접적으로 결합하여 검색 조건을 다룰 수 있습니다. 학원명에 피아노가 포함되고 행정구역이 강남구인 학원을 찾아봅니다.
# query 안에서 문자열 메서드 활용 (engine="python" 지정 권장)piano_gangnam = df.query("행정구역명 == '강남구' and 학원명.str.contains('피아노')", engine="python",)print("강남구 피아노 학원 수:", piano_gangnam.shape[0])print(piano_gangnam["학원명"].head(3))
강남구 피아노 학원 수: 77
5 (주)엠제이재즈피아노실용음악학원
21 CIM피아노학원
276 김윤진피아노교습소
Name: 학원명, dtype: string[pyarrow]
11.6 MultiIndex와 데이터 선택
여러 개의 열을 결합하여 계층적 구조를 가진 MultiIndex를 생성하고, 이에 맞게 데이터를 조회하는 방법입니다.
11.6.1 MultiIndex 생성 및 집계
시도교육청명과 행정구역명을 기준으로 그룹화하여 정원합계의 평균을 집계한 요약 테이블을 생성해 봅니다. 이 과정에서 MultiIndex가 자연스럽게 생성됩니다.
# 그룹화 및 평균 정원 집계grouped_summary = df.groupby( ["시도교육청명", "행정구역명"])[["정원합계", "일시수용능력인원합계"]].mean()print(grouped_summary.head(10))
MultiIndex 구조에서 특정 계층(Level)의 특정 값을 기준으로 데이터를 선택할 때 xs() 메서드가 매우 직관적이고 강력합니다.
# 첫 번째 인덱스 레벨(시도교육청명)에서 '서울특별시교육청'에 해당하는 행만 선택seoul_data = grouped_summary.xs("서울특별시교육청", level="시도교육청명")print(seoul_data.head())# 두 번째 인덱스 레벨(행정구역명)에서 '서구'에 해당하는 행만 선택seogu_data = grouped_summary.xs("서구", level="행정구역명")print(seogu_data.head())