안녕하세요. 데이터CPA, cloud입니다.
지난 글에서는 OpenDART의 API를 활용해서 상장기업 전체의 재무지표 데이터를 수집해보았습니다.
데이터 분석 기초(3) API를 활용한 재무데이터 수집(OpenDART API)
안녕하세요. 데이터CPA, cloud 입니다. 오늘은 OpenDART에서 재무제표와 각종 재무지표를 확인했던 지난 글에 이어서, API를 이용해 대량의 데이터 수집을 자동화하는 방법에 대해 소개하겠습니다. 지
datacpa.tistory.com
하지만 저희가 수집한 것과 같은 원시 데이터(raw data)는 반드시 정제하는 과정을 거쳐야 분석에 쓰일 수 있습니다.
따라서 오늘은 수집한 데이터를 전처리(preprocessing) 하고, 다음 글에서 간단한 탐색적 데이터 분석(EDA) 까지 해보겠습니다. 이를 통해 데이터의 품질을 점검하고, 회계적 인사이트를 얻을 수 있는 기반을 다질 수 있습니다.
목차는 다음과 같습니다.
(1) 데이터 전처리란?
(2) 파이썬을 활용한 전처리 수행
1) 라이브러리 설치
2) 데이터 불러오기
3) 피벗 후 결측치 및 이상치 확인
4) 결측치 제거
5) 이상치 제거
5) 중복값 제거
(1) 데이터 전처리란?
우선 저희가 저번에 수집한 파일을 다시 한 번 보겠습니다.

이 페이지에서 데이터 분석을 방해하는 부분은 어디인 것 같으신가요?
정답은 없지만, 대표적으로는 아래의 세 가지 입니다.
1) 결측치
계속사업이익률을 보면 다른 지표들과 다르게 빈 칸으로 남겨져있다는 것을 볼 수 있습니다. 이를 '결측치'라고 합니다. 데이터가 수집되지 않은 것이죠. 원래 데이터가 없었을 수도, 수집 방법이 잘못되었을 수도 있습니다. 데이터에서 결측치가 발견되면, 이를 어떻게 처리할 지 결정해야 합니다. 아예 그 항목자체를 삭제해버릴 수도 있고, 평균값이나 중앙값으로 대체할 수도 있습니다. 이는 데이터를 분석하고자 하는 목적과 결측치의 형태에 따라 달라질 것입니다.
2) 이상치
이상치는 다른 값들에 비해 너무 크거나 너무 작아서 평균이나 중앙값 등을 왜곡시켜 결과해석에 영향을 주는 값들을 말합니다. 상장기업 중 당기순손실을 기록한 기업들은 위처럼 이익률이 마이너스 값으로 나오게 됩니다. 이 경우 이익률을 바탕으로 한 파생변수들(예: 총 자산 대비 이익률)을 구해보면 모두 마이너스 값이 되어, 결과 해석시 혼동을 주게 됩니다. (예를 들면 총자산이 클수록 이익률은 감소한다는 결과가 나올 수 있습니다.)
3) 자료 형태
이번 데이터 뿐만이 아니라, 일반적으로 데이터를 수집하면 위와 같이 세로로 데이터가 쌓이게 됩니다. 하지만 데이터 수집 후에는 사람들이 보기 편하게 가로 형태로 바꿔주는 게 좋습니다. 데이터를 가로로 바꾸어 주는 것을 피벗(pivot)이라고 하고, 저희가 익숙하게 보는 보고서 형태는 '피벗테이블' 형태이죠. 반대로 데이터를 더 수집하기 위해 세로로 바꾸어 주는 것은 언피벗이라고 합니다.
(2) 파이썬을 활용한 전처리 수행
우선 파이썬을 활용해서 앞에서 말한 항목들에 대한 전처리를 수행해 보겠습니다.
파이썬에서는 기본적으로 판다스(Pandas)를 활용합니다. 이는 저희가 주로 활용할 테이블(표) 형태의 데이터를 다루는 데 아주 유용한 툴입니다.
1) 라이브러리 설치
| !pip install pandas !pip install openpyxl |
오늘 데이터를 분석할 폴더로 가서 주피터 노트북을 실행하고, 위 코드를 복붙하여 실행해줍니다.
주피터노트북이 아닌 다른 파이썬 환경에서는 앞에 느낌표를 지우고 실행해주세요.
python에서 판다스를 이용하고 엑셀파일을 열 수 있게 해줍니다.
2) 데이터 불러오기
아래는 코드 예시입니다. 파일경로에 폴더주소와 마지막에 파일명을 넣어주시면 됩니다.
| import pandas as pd # 파일 경로 file_path = r"C:\Users\LG\Desktop\블로그\데이터전처리\indicaters.xlsx" # 모든 시트를 한 번에 불러오기 sheets_dict = pd.read_excel(file_path, sheet_name=None) # 불러온 시트 이름 확인 print(sheets_dict.keys()) # 특정 시트 DataFrame 꺼내기 (예: 'Sheet1') df_sheet1 = sheets_dict["수익성지표"] df_sheet2 = sheets_dict["안정성지표"] df_sheet3 = sheets_dict["성장성지표"] df_sheet4 = sheets_dict["활동성지표"] # 처음 5행 확인 print(df_sheet1.head()) print(df_sheet2.head()) print(df_sheet3.head()) print(df_sheet4.head()) |
파이썬에서는 데이터를 이렇게 df(data frame)으로 불러와 저장하고 분석합니다. 위 코드를 통해 df_sheet1에는 수익성지표 데이터가 들어가게 된 것이고, 우선 df_sheet1을 전처리해보겠습니다.
3) 피벗 후 결측치와 이상치 확인
현재 데이터는 각기 다른 지표값들이 한 줄로 쭉 나열되어 있어서, 기업별, 지표별로 해석하기가 쉽지 않습니다. 예를 들어 순이익률을 보고 싶다면 첫 번째 기업의 순이익률은 두번째 줄에, 다음 기업의 순이익률은 열일곱번째 줄에 있는 형태죠.

따라서 이를 우선 지표별로 볼 수 있도록 피벗을 해주고, 지표별 결측치와 이상치가 얼마나 있는지 확인해보겠습니다.
| import pandas as pd import numpy as np # === 1) 피벗을 위한 컬럼 자동 탐지 === df = df_sheet1.copy() # 지표 이름 컬럼 후보(예: '세전계속사업이익률', '순이익률' 등) indicator_col = next((c for c in ["idx_nm", "지표명", "indicator", "name"] if c in df.columns), None) # 지표 값 컬럼 후보 value_col = next((c for c in ["idx_val", "값", "value"] if c in df.columns), None) if indicator_col is None or value_col is None: raise KeyError(f"지표명/값 컬럼을 찾을 수 없습니다. 현재 컬럼: {df.columns.tolist()}") # 인덱스(키) 후보: 존재하는 것만 채택 index_candidates = ["corp_code", "corp_name", "stock_code", "bsns_year", "stlm_dt", "reprt_code"] index_cols = [c for c in index_candidates if c in df.columns] if not index_cols: # 최소한 중복 제거를 위해 행 인덱스를 사용 df = df.reset_index().rename(columns={"index": "row_id"}) index_cols = ["row_id"] # 값 숫자 변환(문자/공백/기호 대비) df[value_col] = pd.to_numeric(df[value_col], errors="coerce") # === 2) 피벗 === pv = ( df.pivot_table( index=index_cols, columns=indicator_col, values=value_col, aggfunc="first" # 동일 키-지표에 중복이 있으면 첫 값 사용(필요시 'mean' 등으로 변경) ) .sort_index(axis=1) ) # 멀티인덱스 컬럼 평탄화 if isinstance(pv.columns, pd.MultiIndex): pv.columns = ["_".join(map(str, col)).strip() for col in pv.columns] pv = pv.reset_index() # === 3) 피벗 결과 미리보기 === print("[INFO] Pivoted shape:", pv.shape) print(pv.head()) # === 4) 결측치/이상치(Outlier) 집계: 숫자형 지표만 === num_cols = pv.select_dtypes(include=[np.number]).columns.tolist() # (a) 결측치 개수(열별) na_counts = pv[num_cols].isna().sum().sort_values(ascending=False) print("\n[결측치 개수 상위열]") print(na_counts.head(20)) # (b) 이상치 개수(열별) — IQR 방식 def iqr_outlier_count(series: pd.Series) -> int: s = series.dropna() if s.empty: return 0 q1, q3 = np.percentile(s, [25, 75]) iqr = q3 - q1 if iqr == 0: return 0 lower, upper = q1 - 1.5 * iqr, q3 + 1.5 * iqr return int(((s < lower) | (s > upper)).sum()) outlier_counts = pd.Series({col: iqr_outlier_count(pv[col]) for col in num_cols}) outlier_counts = outlier_counts.sort_values(ascending=False) print("\n[이상치(IQR 기준) 개수 상위열]") print(outlier_counts.head(20)) |
피벗을 해주고 나면 데이터의 형태가 아래 그림과 같이 wide 형태로 바뀐 뒤 df(데이터프레임)에 저장됩니다. 각 기업별로 지표가 보기 좋게 정리되어 있어 지표별로 분석이 가능하게 되었죠. 위의 long 형태와 한번 비교해보시기 바랍니다.

4) 결측치 제거

결측치 갯수 상위열을 보시면,
계속사업이익률: 결측치가 너무 많음 -> 열 자체를 삭제하도록 하겠습니다.
매출총이익률: 전체 행(2613개)의 14%정도를 차지하고 있습니다. 따라서 결측치 처리방법을 신중하게 고려해보아야 합니다.
데이터 전처리는 데이터 수집의 목적과 긴밀하게 연결됩니다. 단순히 업종평균치가 궁금한 것이라면, 평균에 영향이 가지 않도록 결측치를 평균치로 대체할 수 있습니다. 또는 결측치 자체를 하나의 정보로 보고, '특정 업종은 이 항목에 결측치가 많다'는 결론을 내릴 수 있습니다.
저는 앞으로 이 데이터를 활용해 모델링까지 해보고 싶기 때문에, 업종별로 평균치를 구하여 결측치를 대체하도록 하겠습니다. 여러분도 데이터를 수집하기 전, 또는 데이터를 처음 보고 전처리 과정에서라도, 내가 이 분석을 왜 하는지 꼭 생각하며 작업을 진행하신다면 더 좋은 데이터 분석 결과가 나올 수 있을 것입니다.
5) 이상치 제거

여기서 IQR(Interquartile Range, 사분위 범위)이란,
- 데이터의 3사분위수(Q3)와 1사분위수(Q1) 차이: 즉, IQR = Q3−Q1
- 데이터의 **중간 50% 구간(분포의 본체)**을 나타냅니다.
일반적으로 이상치(outlier)는 다음 구간을 벗어나는 값으로 정의합니다:
- 하한값:Q1−1.5×IQR
- 상한값:Q3+1.5×IQR
즉, 데이터가 이 범위를 벗어나면 “이상치”로 판단합니다. (극단 이상치를 잡고 싶을 때는 3 × IQR 기준도 사용함)
위 결과에서 이상치가 높은 항목들은 그만큼 값의 범위가 넓게 퍼져있다는 것입니다.
이는 데이터 자체의 특성일 수 있으므로, 다음 글에서 EDA를 해보고 처리 방법을 결정하겠습니다.
6) 중복값 제거
저희가 수집한 데이터에선 없었지만, 일부 데이터에서는 같은 행이 반복되는 경우가 있습니다. 두 번 집계되었거나, 수집된 데이터를 가공하는 과정에서 발생할 수 있습니다.
따라서 전처리 과정에서 기본적으로 중복값을 확인하고 제거해주는 절차를 거쳐야 합니다.
아래는 파이썬 코드 예시입니다. 윗 코드들과 연결됩니다.
| import pandas as pd import numpy as np # === 0) 원본 복사 === df = df_sheet1.copy() # === 1) 전체(모든 열) 기준 완전 중복 확인/제거 === full_dup_cnt = df.duplicated().sum() print(f"[FULL ROW DUPLICATES] {full_dup_cnt} rows") df_no_full_dups = df.drop_duplicates() print(f"[AFTER DROP (FULL)] shape: {df.shape} -> {df_no_full_dups.shape}") # === 2) '키 열' 기준 중복 확인 === # 상황별 키 자동 선택 (있는 것만 사용) key_candidates = ["corp_code", "stock_code", "corp_name", "bsns_year", "stlm_dt"] keys = [c for c in key_candidates if c in df.columns] # 피벗 전이라 '지표명'이 행에 있다면 중복 판단에 포함 if "idx_nm" in df.columns: keys = keys + ["idx_nm"] if not keys: print("[WARN] 중복 판단용 키 열이 없어 전체 기준으로만 중복 처리했습니다.") keys = df.columns.tolist() # 안전망 print(f"[KEYS USED] {keys}") key_dup_mask = df.duplicated(subset=keys, keep=False) key_dup_cnt = key_dup_mask.sum() print(f"[KEY-BASED DUPLICATES] {key_dup_cnt} rows") # === 2-1) 키 중복 샘플 확인 === show_cols = keys + [c for c in ["rcept_no", "reprt_code", "acc_mt"] if c in df.columns] print("\n[SAMPLE DUP GROUPS]") display( df.loc[key_dup_mask, show_cols] .sort_values(keys) .head(30) ) # === 3) 키 기준 중복 제거: '최신 보고건' 우선 선택 === # 정렬 우선순위: stlm_dt(보고기준일) → reprt_code(보고서 유형) → rcept_no(접수번호) 순 sort_cols = [c for c in ["stlm_dt", "reprt_code", "rcept_no"] if c in df.columns] df_sorted = df.copy() # 날짜형 변환(있으면) if "stlm_dt" in df_sorted.columns: df_sorted["stlm_dt"] = pd.to_datetime(df_sorted["stlm_dt"], errors="coerce") if sort_cols: # 오름차순 정렬 후 keep='last' → 가장 최신/큰 값이 남도록 df_sorted = df_sorted.sort_values(sort_cols) df_dedup = df_sorted.drop_duplicates(subset=keys, keep="last") print(f"\n[AFTER DROP (KEY-BASED)] shape: {df.shape} -> {df_dedup.shape}") # === 4) 확인용: 제거된 행 수 === removed = len(df) - len(df_dedup) print(f"[REMOVED ROWS BY KEY-BASED DEDUP] {removed}") |

여기까지 하면 기본적인 데이터 전처리는 어느 정도 했다고 볼 수 있습니다.
요약해보면, 데이터를 입수했을 땐 우선 파이썬(판다스)에 불러와 데이터 행을 확인합니다.
이후 결측치, 이상치, 중복행을 확인하고 데이터 분석 목적에 따라 어떻게 처리할 지 결정하여 처리하면 됩니다.
잘 따라오셨다면, 이번 글에서 정리한 1번 시트 수익성지표 말고도 2~4번 시트도 모두 직접 전처리하며 연습해보시기 바랍니다.
어려운 건 댓글로 질문 달아주시면 답변드리겠습니다.
다음 글에서는 전처리 된 데이터를 가지고 EDA라는 기본적인 분석을 시작해보도록 하겠습니다.
EDA와 데이터 전처리, 무엇이 다를까? | 데이터 분석 기초(5)
안녕하세요. 데이터CPA, Cloud입니다. 데이터 분석을 시작할 때 가장 많이 헷갈리는 개념이 바로 EDA(탐색적 데이터 분석)와 데이터 전처리입니다. 둘 다 필수 과정이지만 목적과 역할은 전혀 다릅
datacpa.tistory.com
'AI & Data analystics > Data' 카테고리의 다른 글
| 서울시 공영주차장 실시간 현황 지도 만들기 | 데이터 분석 기초(6) (1) | 2025.09.08 |
|---|---|
| EDA와 데이터 전처리, 무엇이 다를까? | 데이터 분석 기초(5) (2) | 2025.09.05 |
| API를 활용한 재무데이터 수집(OpenDART API) | 데이터 분석 기초(3) (1) | 2025.09.02 |
| openDART에서 데이터 수집하기(feat.XBRL) | 데이터 분석 기초(2) (4) | 2025.09.01 |
| 아나콘다 & 주피터 노트북(Jupyter Notebook) 설치 및 설정 | 데이터 분석 기초(1) (3) | 2025.08.24 |