데이터 분석가로서 로컬 파일(CSV) 관리의 한계를 넘어, 엔터프라이즈급 데이터베이스(PostgreSQL)를 장고(Django)로 제어하고, Streamlit으로 시각화하는 과정을 공부해봅니다.
프로젝트 구조
mysite/
├── manage.py
├── config/ # Django 설정 (settings.py 등)
├── heart_app/ # Django 앱 (models.py 등)
├── utils/ # 유틸리티
│ ├── cache.py # DB 로딩 및 캐싱
│ ├── common.py # 전처리 함수
│ └── styles.css # CSS 스타일
├── pages/ # Streamlit 페이지
│ ├── st_dashboard.py # 대시보드
│ ├── st_analysis.py # 분석
│ └── st_predict.py # 예측
├── app.py # Streamlit 메인 (네비게이션)
└── db_insert.py # 데이터 초기 적재 스크립트
1. Django ORM 뼈대 만들기 & PostgreSQL 연동
가장 먼저 할 일은 장고와 DB를 연결하는 것입니다. 의료 데이터는 보안과 안정성이 중요하므로 PostgreSQL을 사용합니다.
1-1. 라이브러리 설치
pip install django psycopg2-binary
1-2. settings.py 수정 (DB 연결)
SQLite 대신 PostgreSQL 정보를 입력합니다. (PostgreSQL은 미리 설치되어 있어야 합니다.)
# config/settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'medical_db', # 생성한 DB 이름
'USER': 'postgres', # DB 사용자명
'PASSWORD': 'your_password',# DB 비밀번호
'HOST': 'localhost',
'PORT': '5432',
}
}
INSTALLED_APPS = [
# ...
'heart_app', # 앱 등록 필수!
]
2. ORM Table 만들기 (models.py)
심장질환 데이터를 저장할 테이블을 정의합니다. SQL을 몰라도 파이썬 클래스로 정의하면 테이블이 생성됩니다.
# heart_app/models.py
from django.db import models
class HeartData(models.Model):
# 환자 식별 정보
patient_id = models.AutoField(primary_key=True)
# 임상 데이터
age = models.IntegerField(verbose_name="나이")
sex = models.IntegerField(verbose_name="성별(1:남,0:여)")
cp = models.IntegerField(verbose_name="흉통유형(0-3)")
trtbps = models.IntegerField(verbose_name="혈압")
chol = models.IntegerField(verbose_name="콜레스테롤")
fbs = models.IntegerField(verbose_name="공복혈당(1:>120)")
# 타겟 데이터 (0:정상, 1:질환)
output = models.IntegerField(verbose_name="진단결과")
def __str__(self):
return f"Patient {self.patient_id} - Risk: {self.output}"
작성 후 터미널 실행:
python manage.py makemigrations
python manage.py migrate
3. CSV to Table 데이터 채우기 (db_insert.py)
가상의 의료 데이터를 생성하고, Django ORM의 bulk_create를 사용해 DB에 고속으로 적재합니다.
# db_insert.py
import os
import django
import pandas as pd
import numpy as np
# 1. Django 환경 설정 (Streamlit이나 스크립트에서 ORM 쓸 때 필수!)
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
django.setup()
from heart_app.models import HeartData
def generate_fake_data(n=1000):
np.random.seed(42)
df = pd.DataFrame({
'age': np.random.randint(29, 77, n),
'sex': np.random.choice([0, 1], n),
'cp': np.random.choice([0, 1, 2, 3], n),
'trtbps': np.random.randint(94, 200, n),
'chol': np.random.randint(126, 564, n),
'fbs': np.random.choice([0, 1], n, p=[0.85, 0.15]),
'output': np.random.choice([0, 1], n)
})
return df
if __name__ == "__main__":
print("데이터 생성 및 DB 적재 시작...")
# 1. 데이터 생성
df = generate_fake_data()
# 2. DataFrame -> Django Model Instance 변환
instances = [
HeartData(
age=row['age'], sex=row['sex'], cp=row['cp'],
trtbps=row['trtbps'], chol=row['chol'],
fbs=row['fbs'], output=row['output']
)
for _, row in df.iterrows()
]
# 3. Bulk Insert (고속 저장)
HeartData.objects.all().delete() # 기존 데이터 삭제 (초기화)
HeartData.objects.bulk_create(instances)
print(f"총 {len(instances)}건의 환자 데이터가 PostgreSQL에 저장되었습니다.")
4. [Utils] Streamlit과 Django 연결고리 (cache.py)
이제 utils/cache.py를 수정하여 CSV가 아닌 DB에서 데이터를 가져오도록 변경합니다.
# utils/cache.py
import streamlit as st
import pandas as pd
import os
import django
# Django Setup (한 번만 실행되도록 체크)
if not os.environ.get('DJANGO_SETTINGS_MODULE'):
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
django.setup()
from heart_app.models import HeartData
@st.cache_data
def load_medical_data_orm():
"""
Django ORM을 통해 PostgreSQL에서 데이터를 조회하고 DataFrame으로 변환
"""
# QuerySet 조회 (SELECT * FROM heart_app_heartdata)
qs = HeartData.objects.all().values()
# DataFrame 변환
df = pd.DataFrame(list(qs))
return df
@st.cache_data
def load_ai_model(pkl_path):
import joblib
return joblib.load(pkl_path)
5. 대시보드 웹 프로그래밍 & 스타일 변경 (st_dashboard.py)
DB에서 실시간으로 집계된 데이터를 보여주는 대시보드입니다. utils/styles.css를 적용해 디자인을 변경합니다.
utils/styles.css (사용자 업로드 파일 활용)
/* Metric 카드 스타일 변경 */
div[data-testid="stMetric"] {
background-color: #f0f2f6;
border: 1px solid #d1d5db;
padding: 15px;
border-radius: 10px;
box-shadow: 2px 2px 5px rgba(0,0,0,0.1);
}
pages/st_dashboard.py
import streamlit as st
import plotly.express as px
from utils.cache import load_medical_data_orm
def app():
# CSS 적용 (common.py의 write_style 활용 가능)
with open('utils/styles.css') as f:
st.markdown(f'<style>{f.read()}</style>', unsafe_allow_html=True)
st.header("심장질환 데이터 대시보드 (DB 연동)")
# 1. DB에서 데이터 로드
df = load_medical_data_orm()
# 2. KPI 지표 (Metrics)
st.subheader("실시간 환자 현황")
col1, col2, col3, col4 = st.columns(4)
col1.metric("총 데이터 수", f"{len(df)}건")
col2.metric("유질환자 비율", f"{(df['output'].mean()*100):.1f}%")
col3.metric("평균 혈압", f"{df['trtbps'].mean():.0f} mmHg")
col4.metric("평균 콜레스테롤", f"{df['chol'].mean():.0f} mg/dL")
st.markdown("---")
# 3. 차트 시각화
c1, c2 = st.columns(2)
with c1:
st.caption("연령대별 환자 분포")
fig1 = px.histogram(df, x="age", nbins=10, color="output",
title="Age Distribution by Risk")
st.plotly_chart(fig1, use_container_width=True)
with c2:
st.caption("흉통 유형별 위험도")
risk_by_cp = df.groupby('cp')['output'].mean().reset_index()
fig2 = px.bar(risk_by_cp, x='cp', y='output',
title="Risk Probability by Chest Pain Type")
st.plotly_chart(fig2, use_container_width=True)
if __name__ == "__main__":
app()
6. 분석 웹 프로그래밍 (st_analysis.py)
st_analysis.py의 구조를 가져와 의료 데이터 분석 페이지로 만듭니다.
# pages/st_analysis.py
import streamlit as st
import plotly.express as px
from utils.cache import load_medical_data_orm
def app():
st.header("심층 데이터 분석")
# DB 데이터 로드
df = load_medical_data_orm()
# 사이드바 필터
with st.sidebar:
st.subheader("필터 설정")
gender_select = st.multiselect("성별 선택 (0:여, 1:남)",
options=df['sex'].unique(),
default=df['sex'].unique())
# 데이터 필터링
df_filtered = df[df['sex'].isin(gender_select)]
# 탭 구성
tab1, tab2 = st.tabs(["데이터 브라우저", "상관관계 분석"])
with tab1:
st.write(f"조회된 데이터: {len(df_filtered)}건")
st.dataframe(df_filtered, use_container_width=True)
with tab2:
st.caption("혈압과 콜레스테롤의 상관관계")
fig = px.scatter(df_filtered, x='trtbps', y='chol', color='output',
size='age', hover_data=['patient_id'],
title="Blood Pressure vs Cholesterol")
st.plotly_chart(fig, use_container_width=True)
if __name__ == "__main__":
app()
7. 예측 웹 프로그래밍 (st_predict.py)
st_predict.py 구조를 활용하여, 사용자가 입력한 정보를 바탕으로 심장병을 예측합니다. (모델은 미리 학습되어 heart_model.pkl로 저장되었다고 가정)
# pages/st_predict.py
import streamlit as st
import pandas as pd
import numpy as np
import joblib
# 모델 로드 (가상 로직 또는 실제 파일 로드)
def load_model():
# 실제로는: return joblib.load('heart_model.pkl')
pass
def app():
st.header("AI 심장질환 예측")
# 1. 사용자 입력 폼 (위젯)
with st.form("predict_form"):
c1, c2 = st.columns(2)
with c1:
age = st.number_input("나이", 20, 100, 50)
sex = st.selectbox("성별", [0, 1], format_func=lambda x: "남성" if x==1 else "여성")
cp = st.selectbox("흉통 유형", [0, 1, 2, 3])
with c2:
trtbps = st.number_input("혈압 (mmHg)", 80, 250, 120)
chol = st.number_input("콜레스테롤", 100, 600, 200)
fbs = st.selectbox("공복혈당 > 120", [0, 1], format_func=lambda x: "예" if x==1 else "아니오")
submit = st.form_submit_button("진단 결과 확인")
# 2. 예측 실행
if submit:
# 입력 데이터 DataFrame 변환
input_data = pd.DataFrame([[age, sex, cp, trtbps, chol, fbs]],
columns=['age', 'sex', 'cp', 'trtbps', 'chol', 'fbs'])
# (실습용) 가상의 예측 로직
# 실제로는: pred = model.predict(input_data)[0]
# prob = model.predict_proba(input_data)[0][1]
prob = np.random.rand()
st.markdown("---")
st.subheader("분석 결과")
col1, col2 = st.columns([1, 2])
with col1:
st.metric("발병 확률", f"{prob*100:.1f}%")
with col2:
if prob > 0.5:
st.error("고위험군으로 의심됩니다. 정밀 검사가 필요합니다.")
else:
st.success("저위험군입니다. 건강한 상태를 유지하세요.")
if __name__ == "__main__":
app()
전체 실행 방법
- DB 세팅: db_insert.py를 실행해 PostgreSQL에 데이터를 채웁니다.
- 앱 실행: streamlit run app.py (메인 네비게이션 파일 생성 필요)
헷갈리실 수 있는 부분을 정리
1. 왜 Django로 실행(runserver) 안 하나요?
우리의 목적이 "데이터 분석 대시보드"이기 때문입니다.
- Django(runserver): 홈페이지, 회원가입, 게시판 같은 정통 웹사이트를 만들 때 실행합니다. (HTML/CSS를 다 짜야 해서 분석가에게는 진입장벽이 높음)
- Streamlit(run): 데이터 시각화, AI 데모를 만들 때 실행합니다. (파이썬만으로 화면이 뚝딱 만들어짐)
그래서 우리는 "DB 관리는 짱짱한 Django에게 맡기고, 화면은 편한 Streamlit으로 그리자!"는 전략을 취한 것
2. 작동 원리 (연결고리)
Streamlit이 어떻게 Django를 가져다 쓰는지 궁금하시죠? 바로 utils/cache.py나 db_insert.py 파일 맨 위에 있는 이 3줄의 마법 덕분입니다.
import os
import django
# 1. "나 Django 설정 파일 좀 갖다 쓸게!" 라고 선언
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
# 2. Django 기능 깨우기 (ORM 엔진 시동)
django.setup()
# 3. 이제부터 Django 모델(테이블)을 내 것처럼 쓸 수 있음
from heart_app.models import HeartData
이 코드가 들어가면, Streamlit 파일(app.py) 안에서도 HeartData.objects.all() 같은 Django 명령어가 작동하게 됩니다.
3. 역할 분담 요약
이 프로젝트에서 두 프레임워크는 철저하게 분업합니다.
| 구분 | 역할 |
| Django | - DB 테이블 생성 - 데이터 저장/수정 관리 |
| Streamlit | - 그래프 그리기 - 사용자 입력 받기 |
결론
- Django로 공사하기: 터미널에서 migrate 명령어로 DB 테이블을 만듭니다. (데이터 저장소 준비)
- Streamlit으로 영업하기: 터미널에서 streamlit run app.py를 입력해서 화면을 띄웁니다. (사용자에게 보여주기)
'Study Note > Streamlit' 카테고리의 다른 글
| [Streamlit 대시보드] 숫자로 말하는 데이터: st.metric과 멀티페이지 앱(MPA) 설계 (0) | 2025.12.29 |
|---|---|
| AI 모델, 직접 만들어서 웹으로 배포하기 (A to Z) (1) | 2025.12.29 |
| "내 앱은 왜 자꾸 까먹을까?" : Session State와 Caching 완벽 해부 (0) | 2025.12.29 |
| 속도는 올리고 코드는 깔끔하게 (0) | 2025.12.29 |
| 의료 데이터 분석, 웹으로 띄우기 (기초편) (0) | 2025.12.29 |