재무제표, 파이썬 한 줄

Quick Summary

삼성전자 자본총계가 연결 486조 별도 284조로 갈리는 이유부터, dartlab으로 손익계산서 재무상태표 현금흐름표를 브라우저에서 파이썬 한 줄로 열어 계정과 기간 숫자를 확인하는 법까지 익힌다.

자본총계가 486조이기도 하고 284조이기도 하다

삼성전자 자본총계를 검색하면 어떤 곳은 약 486조 원, 어떤 곳은 약 284조 원이라고 적혀 있다. 200조 원이 넘게 벌어지는데, 둘 중 하나가 틀린 것이 아니라 둘 다 삼성전자가 공시한 진짜 숫자다. 차이는 자회사에서 온다. 486조는 전 세계 자회사까지 하나로 묶어서 본 값이고, 284조는 삼성전자 법인 하나만 떼어 본 값이다. 앞을 연결, 뒤를 별도라고 부른다.

인터넷에서 본 숫자와 내가 직접 뽑은 숫자가 안 맞을 때, 열에 아홉은 이것 때문이다. 한쪽은 연결을, 다른 쪽은 별도를 보고 있다. 그래서 재무제표를 부를 때 가장 먼저 물어야 하는 것은 계정 이름도 기간도 아니라 “이 표가 연결인가 별도인가”다. 이번 편은 손익계산서, 재무상태표, 현금흐름표를 브라우저에서 직접 열고, 그 표가 무엇을 보고 있는지 읽는 법을 익힌다. 아래 코드 칸은 전부 이 브라우저 안에서 그대로 돈다.

회계 교과서는 이 세 장을 각각 얼마를 벌었고(손익계산서), 무엇을 가졌고(재무상태표), 돈이 실제로 어디로 갔는지(현금흐름표) 보여 준다고 가르친다. 맞는 설명이다. 그런데 교과서가 잘 말하지 않는 것이 방금 그 486조와 284조 같은 함정이다. 같은 회사의 같은 계정인데, 어느 범위를 보느냐에 따라 숫자가 갈린다.

게다가 세 장은 각각 한 시점의 사진이다. 사업보고서 하나를 열면 올해와 작년, 두 해치만 나란히 있다. 5년을 보려면 보고서 다섯 개를 열어야 하고, 그 순간 표는 사람 손을 떠난다. dartlab은 이 문제를 다르게 푼다. 회사 하나의 공시를 미리 읽어서, 왼쪽에는 계정 이름, 오른쪽에는 기간별 숫자가 붙은 큰 표 한 장으로 만들어 둔다. 손익계산서를 부르면 그 표의 손익 부분이 나온다. 두 해치가 아니라 저장된 기간을 한 번에 본다. 그래서 재무제표는 세 장이 아니라, 부르는 각도만 다른 한 장이다.

계정과 기간이 만나는 격자

지난 편, DART 공시분석, 설치 없이에서 이 표를 한 번 열어 봤다. 이번 편은 그 표를 손익계산서, 재무상태표, 현금흐름표로 나눠 직접 열어 보고, 화면에서 어디를 봐야 하는지 익히는 편이다.

세 장을 각각 불러 본다

먼저 회사를 잡고 손익계산서를 연다.

import dartlab

c = dartlab.Company("005930")   # 삼성전자
c.panel("IS").head(5)           # 손익계산서. 앞에서 다섯 줄만

처음 실행은 20초쯤 걸린다. 파이썬과 dartlab을 브라우저로 내려받는 시간이다. 그다음부터는 즉시 돈다.

panel이 이 글의 핵심이다. "IS"는 손익계산서, "BS"는 재무상태표, "CF"는 현금흐름표다. 세 글자만 바꾸면 다른 표가 열린다. 이 세 장이 각각 무엇을 말하는지는 사업보고서 읽는 법에 정리해 두었다.

c.panel("BS").head(5)   # 재무상태표
c.panel("CF").head(5)   # 현금흐름표

세 표는 보는 방법이 같다. 왼쪽에는 계정 이름이 있고, 오른쪽에는 기간별 숫자가 있다. 손익계산서에서 익힌 방법을 재무상태표와 현금흐름표에도 그대로 쓴다.

표의 앞부분을 먼저 본다

표를 다루기 전에 먼저 앞줄을 본다. 어떤 계정이 있고, 최근 기간 숫자가 어떻게 붙어 있는지 눈으로 확인하는 것이다.

is_ = c.panel("IS")
is_.head(8)

앞쪽 줄을 보면 회사가 어떤 이름을 쓰는지 바로 보인다. 매출액, 매출원가, 매출총이익 같은 줄이 실제 숫자와 함께 나온다.

여기서 돌려받는 표는 polars DataFrame 이다. head, columns, null_count 같은 것이 전부 거기서 온다.

처음에는 표가 몇 줄인지 세는 것보다 실제 줄을 보는 편이 낫다. 회사마다 계정 이름이 다르고, 상장한 지 얼마 안 된 회사는 과거 기간이 짧다. 앞부분을 먼저 보면 없는 계정이나 없는 기간을 찾느라 헤매는 일을 줄일 수 있다.

연간과 분기를 나눠 본다

큰 표를 먼저 봤다면, 그다음에는 기간 기준을 바꿔 본다.

c.panel("IS", freq="Y").head(8)

읽는 법은 이렇다. "IS"는 손익계산서이고, freq="Y"는 연간으로 묶으라는 뜻이다. 연간 표는 한 해를 한 칸으로 놓고 큰 방향을 볼 때 쓴다.

필요한 계정을 정확히 고르는 일은 뒤에서 따로 배운다. 이 편에서는 먼저 panel로 표를 열고, 왼쪽 계정 이름과 오른쪽 기간 값을 눈으로 확인하는 것까지만 잡는다.

분기로 보고 싶으면 기간 글자만 바꾼다.

c.panel("IS", freq="Q").head(8)

연간과 분기는 보는 칸이 다르다. 연간은 1년에 한 칸이고, 분기는 1년에 여러 칸이다. 매출이 몇 년 동안 늘었는지 볼 때와 최근 분기가 꺾였는지 볼 때는 서로 다른 표를 봐야 한다. 필요한 줄을 정확히 찾는 방법은 재무제표 계정, 코드로 찾기에서 이어진다.

계정 이름이 회사마다 다르다는 문제

여기서 초보자가 반드시 만나는 벽이 있다. 회사마다 같은 것을 다른 이름으로 적는다.

어떤 회사는 “매출액”이라 쓰고, 어떤 회사는 “영업수익”이라 쓴다. 은행은 “이자수익”부터 시작한다. 공시는 회사가 쓴 그대로 올라오기 때문에, 이름으로 찾으면 어떤 회사에서는 찾히고 어떤 회사에서는 안 찾힌다. 원본이 궁금하면 DART에서 그 회사의 사업보고서를 열어 재무제표 주석을 보라. 우리가 지금 다루는 표가 거기서 나온 것이다.

dartlab은 이 문제를 표준 이름 한 줄을 더 붙여서 푼다. 표의 왼쪽 첫 칸을 보라. sales, cost_of_sales, gross_profit 같은 영문 이름이 있다. 이것이 dartlab이 붙인 표준 이름이고, 그 옆의 한글이 회사가 실제로 공시에 적은 이름이다.

c.panel("IS").columns[:4]   # 표의 앞쪽 열 이름

그래서 여러 회사를 비교할 때는 한글 이름이 아니라 표준 이름으로 잡는 것이 안전하다. 예를 들어 제조사의 sales와 은행의 sales는 한글로는 “매출액”과 “영업수익”으로 전혀 달라 보이지만, 표준 이름으로는 같은 자리에 정렬된다. 삼성전자와 은행을 한 표에 놓고 비교하려면 이 표준 이름 축이 있어야 줄이 어긋나지 않는다. 이 매핑은 사람이 검수한다. 자동 추론에 맡기면 “영업수익”을 “영업이익”으로 잘못 이어 붙이는 사고가 조용히 난다.

지금 보고 있는 표가 연결인가 별도인가

여기가 초보자가 가장 크게 다치는 자리다. 같은 회사의 같은 계정인데 숫자가 두 개다.

연결은 자회사까지 하나의 회사처럼 묶어서 본 재무제표다. 별도는 그 회사 법인 하나만 본 것이다. 삼성전자는 전 세계에 자회사를 두고 있고, 그 자회사들의 자산과 이익이 연결에는 들어가고 별도에는 안 들어간다. 이 두 장을 언제 어떻게 만드는지는 회사가 임의로 정하는 것이 아니라 국제회계기준이 규정한다.

dartlab은 아무 말이 없으면 연결을 준다. 별도를 보려면 그렇게 말해야 한다.

bs = c.panel("BS", freq="Y")                       # 연결
bs.head(5)
bs_separate = c.panel("BS", freq="Y", scope="separate")     # 별도
bs_separate.head(5)

두 표를 나란히 두면 서두의 486조와 284조가 눈앞에서 갈라진다. 2026년 1분기 기준으로 연결 자본총계는 약 486조 원, 별도는 약 284조 원, 그 200조 원의 차이가 자회사에서 온다. 위 코드 두 줄이 만드는 것이 정확히 그 두 표다.

어느 쪽이 맞느냐는 질문은 틀렸다. 무엇을 묻고 있느냐가 답을 정한다. 그룹 전체의 체력을 보고 싶으면 연결, 그 법인 자체의 재무 상태를 보고 싶으면 별도다. 그리고 검색으로 찾은 숫자와 내가 뽑은 숫자가 어긋날 때 가장 먼저 의심할 것도 이 한 칸, scope다.

연결과 별도의 차이

빈칸은 빈칸이다

표를 보다 보면 값이 없는 칸을 만난다. 이때 두 가지를 구분해야 한다.

회사가 그 계정을 안 쓴 경우. 제조업 회사의 손익계산서에 “이자수익” 항목이 없는 것은 정상이다. 없는 것이지 누락이 아니다.

그 기간에 공시가 없는 경우. 상장 전 기간, 혹은 아직 결산이 안 끝난 최근 분기가 여기 해당한다.

dartlab은 두 경우 모두 빈칸으로 둔다. 0으로 채우지 않는다. 0으로 채우면 “이자수익 0원”과 “이자수익 항목 없음”이 구별되지 않고, 평균을 내는 순간 조용히 틀린 값이 나오기 때문이다.

bs = c.panel("BS")
bs.null_count().to_dicts()[0]   # 열마다 빈칸이 몇 개인지

빈칸의 개수가 곧 그 회사 공시의 성긴 정도다. 오래된 기간일수록 빈칸이 많고, 상장한 지 얼마 안 된 회사는 앞쪽 기간이 통째로 비어 있다. 그래서 빈칸을 0으로 바꿔 평균을 내면, 사실은 자료가 없는 기간을 실적이 0인 기간으로 오해하게 된다.

회사를 바꿔 본다

지금까지의 코드에서 종목코드 한 곳만 바꾸면 대상이 바뀐다.

sk = dartlab.Company("000660")            # SK하이닉스
sk.panel("IS", freq="Y").head(5)

005930000660이 되었을 뿐인데 다른 회사의 손익계산서가 나온다. 회사가 바뀌어도 보는 방법은 그대로다. 종목코드 하나로 회사를 지목하는 이유는 회사 하나는 종목코드 하나에 정리해 두었다.

미국 회사는 티커를 넣는다. 그 경우 데이터가 EDGAR에서 온다. 다만 지금 이 브라우저에서는 미국 회사 데이터를 받는 데 시간이 더 걸리므로, 이번 편에서는 국내 회사로 익힌다. 미국 공시의 구조는 EDGAR 전부 알기에 따로 적었다.

자주 걸리는 함정 셋

panel에 있는 숫자는 회사가 공시한 값 그대로다. dartlab이 다시 계산하거나 보정하지 않으므로, 회사가 정정공시를 내면 그 값이 바뀐다.

연간 값은 분기 네 개의 합과 다를 수 있다. 감사 과정에서 조정이 들어가면 합이 안 맞는데, 이것은 오류가 아니라 회계의 사실이다.

최근 분기가 비어 있어도 회사에 문제가 있는 것이 아니다. 결산과 공시에는 시차가 있어서, 오늘 열어 본 표의 마지막 칸은 대개 두세 달 전 분기다.

다음 편에서 할 것

이번 편에서 세 장의 표를 열고, 연결과 별도를 갈라 보고, 계정과 기간 숫자와 빈칸을 읽는 법을 익혔다. 브라우저 밖에서 더 큰 데이터를 다루고 싶으면 pip install dartlab 로 설치본을 쓰고, 그 첫걸음은 dartlab 쉽게 시작하기에 있다.

다음은 그 표로 무엇을 계산할 것인가다. 매출은 얼마나 빨리 늘고 있는가, 마진은 개선되고 있는가, 그 개선이 진짜인가. dartlab이 이 질문들을 미리 계산해 둔 것을 부르는 법을 다룬다. 그 전에 위 코드 칸에서 계정 이름을 바꿔, "매출액" 자리에 "영업이익"이나 "당기순이익"을 넣으면 무엇이 나오는지, 없는 이름을 넣으면 어떻게 되는지 직접 확인해 보는 것이 이번 편의 숙제다.

오늘 기억할 한 줄: 재무제표를 부를 때 계정 이름보다 먼저 물어야 하는 것은 이 표가 연결인가 별도인가다.


이 글은 특정 종목의 매수·매도를 권유하지 않습니다.

양질의 기업분석을 계속 공유하기 위해 노력하고 있습니다. 도움이 되셨다면 dartlab을 후원해주세요.

Buy Me A Coffee
같은 시리즈에서 이어 읽기
DartLab

같은 카테고리에서 더 읽기

DartLab은 dartlab 이야기 카테고리 안에서 글이 서로 이어지도록 설계합니다. 다음 글로 넘어가며 구조와 맥락을 같이 쌓는 방식입니다.

DartLab Product

이 글의 판단을 실제 데이터 흐름으로 옮기기

DartLab은 전자공시를 읽는 법을 코드와 데이터로 연결하기 위해 만든 제품입니다. 사업보고서 텍스트, 재무 시계열, 정기보고서 데이터를 한 흐름에서 다루도록 설계했습니다.