RyanNerd
라덕'Story
RyanNerd
  • 분류 전체보기 (117)
    • Study Note (80)
      • Python (3)
      • R (1)
      • Airflow (7)
      • 통계 (26)
      • Machine Learning (16)
      • Streamlit (9)
      • Django (1)
      • Deep Learning (12)
      • dbt (2)
      • SQL (3)
    • 빅데이터분석기사 (1)
      • 필기 (1)
    • Programmers (28)
      • Python (13)
      • SQL (15)
    • Project (3)
      • Django (3)
    • Mac (2)

블로그 메뉴

  • NaverBlog
  • 홈

최근 글

전체 방문자
오늘
어제
hELLO · Designed By 정상우.
RyanNerd

라덕'Story

Study Note/Streamlit

[Django x Streamlit] 의료 데이터 분석 플랫폼 구축하기: ORM부터 웹 대시보드까지

2025. 12. 30. 13:48

데이터 분석가로서 로컬 파일(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()

 

전체 실행 방법

  1. DB 세팅: db_insert.py를 실행해 PostgreSQL에 데이터를 채웁니다.
  2. 앱 실행: 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 - 그래프 그리기
- 사용자 입력 받기

 

결론

  1. Django로 공사하기: 터미널에서 migrate 명령어로 DB 테이블을 만듭니다. (데이터 저장소 준비)
  2. 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
    'Study Note/Streamlit' 카테고리의 다른 글
    • [Streamlit 대시보드] 숫자로 말하는 데이터: st.metric과 멀티페이지 앱(MPA) 설계
    • AI 모델, 직접 만들어서 웹으로 배포하기 (A to Z)
    • "내 앱은 왜 자꾸 까먹을까?" : Session State와 Caching 완벽 해부
    • 속도는 올리고 코드는 깔끔하게
    RyanNerd
    RyanNerd
    라이언 덕후의 일상 스토리~

    티스토리툴바