Week 04 — 임상 데이터 변환기 + 분석 파이프라인
이번 주는 두 가지 앱을 다룹니다.
Part 1 — 측정 소프트웨어가 뱉은 “지저분한 파일”을 R/SPSS에 넣을 수 있는 형태로 바꿔주는 변환기 (
app.py) Part 2 — 장비 Raw Data에서 바로 취합 → 통계 → 시각화까지 이어지는 분석 파이프라인 (app_analysis.py)Part 1에서 pandas 기본과 정규식을 익히고, Part 2에서 실전 데이터로 확장합니다.
사전 준비
cd tutorials/week04-pandas
pip install streamlit pandas openpyxl plotly scipy streamlit-sortables
pandas 기본 개념
pandas란?
pandas = Python에서 표(테이블) 형태의 데이터를 다루는 라이브러리입니다.
이름은 Panel Data(패널 데이터, 시간에 따라 반복 측정된 데이터)에서 왔습니다. 임상시험 데이터처럼 “피험자 × 방문차수” 구조가 딱 여기에 해당합니다.
라이브러리(Library) = 누군가 미리 만들어둔 기능 묶음. Excel에서 피벗 테이블, VLOOKUP 같은 기능이 내장된 것처럼, pandas는 Python에 “표 다루는 기능”을 통째로 추가해줍니다.
pandas를 쓰면 Excel로 하기 어려운 작업 — 수백 개 파일 한 번에 처리, 복잡한 패턴 추출, 반복 자동화 — 을 코드 몇 줄(또는 AI 프롬프트 한 번)로 처리할 수 있습니다.
DataFrame이란?
DataFrame = Excel 시트와 같은 2차원 표 구조입니다.
| 용어 | Excel에서 | pandas에서 |
|---|---|---|
| 표 전체 | 시트(Sheet) | DataFrame |
| 가로 한 줄 | 행(Row) | row |
| 세로 한 줄 | 열(Column) | column |
| 한 칸 | 셀(Cell) | 값(value) |
pandas로 할 수 있는 것
데이터 탐색 — 파일을 열고 구조를 파악하는 작업
| 하고 싶은 일 | 실제 함수 | Excel 비유 | AI에게 이렇게 요청하세요 |
|---|---|---|---|
| 파일 불러와서 표로 보기 | pd.read_csv / read_excel |
파일 열기 | “sample.csv 읽어서 처음 5행 보여줘” |
| 행 수·열 수 확인 | df.shape |
우하단 셀 주소 | “몇 행 몇 열인지 알려줘” |
| 열 이름 목록 확인 | df.columns |
헤더 행 보기 | “어떤 열이 있는지 목록 보여줘” |
| 특정 열·행만 선택 | df["열명"] / df[조건] |
열 선택 / 필터 | “Visit이 V1인 행만 보여줘” |
| 값 기준 정렬 | df.sort_values("열명") |
정렬 기능 | “수분량 높은 순서로 정렬해줘” |
| 숫자 분포·평균 요약 | df.describe() |
기술통계 함수 | “각 열의 평균, 최솟값, 최댓값 요약해줘” |
| 그룹별 통계 | df.groupby("열명").mean() |
피벗 테이블 | “Visit별로 수분량 평균 구해줘” |
| 결측값 위치 파악 | df.isnull().sum() |
빈 셀 찾기 | “빈 칸이 어느 열에 몇 개 있는지 알려줘” |
데이터 변환 — 형식과 구조를 바꾸는 작업
| 하고 싶은 일 | 실제 함수 | Excel 비유 | AI에게 이렇게 요청하세요 |
|---|---|---|---|
| 열 이름 바꾸기 | df.rename(columns={...}) |
헤더 셀 편집 | “moisture_val 열 이름을 수분량으로 바꿔줘” |
| 열 순서 재배치 | df[[col1, col2, ...]] |
열 잘라서 붙여넣기 | “열 순서를 이 순서로 바꿔줘: ID, 이름, 성별…” |
| 값에서 숫자만 추출 | str.extract(pattern) |
MID / FIND 조합 | “45.2 AU에서 숫자 45.2만 뽑아줘” |
| 한 열을 여러 열로 분리 | str.extract(pattern) |
텍스트 나누기 | “S001_김민지(F/28)에서 ID·이름·성별·나이를 각각 열로 분리해줘” |
| 텍스트 값을 숫자 코드로 | df[col].map({...}) |
찾기·바꾸기 | “없음→0, 경미→1, 중등도→2로 바꿔줘” |
| 세로로 긴 형태로 변환 | df.melt(...) |
피벗 테이블 반전 | “시점별 열을 하나의 열로 합쳐서 Long format으로 바꿔줘” |
문제 상황 — 이 파일, 그대로 쓸 수 있을까?
측정 장비나 수집 소프트웨어가 내보낸 파일은 대부분 이렇게 생겼습니다.
Subject_Info,Visit,측정일,moisture_val,TEWL_val,sebum_val,skin_type,adverse_event,담당연구원
S001_김민지(F/28),V1,2024/3/5,45.2 AU,8.3 g/m²h,12.1 μg/cm²,건성,없음,이수진
S001_김민지(F/28),V2,2024/3/19,48.1 AU,7.9 g/m²h,11.4 μg/cm²,건성,없음,이수진
S001_김민지(F/28),V3,2024-04-02,51.3 AU,7.2 g/m²h,10.8 μg/cm²,건성,없음,이수진
S003_박서연(F/42),V1,2024/3/6,52.8 AU,6.9 g/m²h,9.3 μg/cm²,중성,,이수진
S004_최동현(M/29),V2,2024/3/20,,9.4 g/m²h,19.8 μg/cm²,복합성,없음,박민준
R이나 SPSS에 그대로 넣으면 오류가 납니다. 왜 그럴까요?
문제 1 — 피험자 정보 4개가 한 열에 뭉쳐 있다
Subject_Info
S001_김민지(F/28)
Subject_Info 열 하나에 피험자 ID · 이름 · 성별 · 나이가 구분자(_와 괄호)로 이어져 있습니다.
통계 프로그램에서 “성별로 그룹 나누기”, “피험자 ID로 데이터 매칭”을 하려면 각각의 정보가 별도 열에 있어야 합니다.
원본 한 열 → 분리 후 네 열
─────────────────────────────────────────────────────
S001_김민지(F/28) → S001 | 김민지 | F | 28
문제 2 — 측정값에 단위가 붙어 있다
moisture_val TEWL_val sebum_val
45.2 AU 8.3 g/m²h 12.1 μg/cm²
숫자처럼 보이지만, 뒤에 AU · g/m²h · μg/cm² 같은 단위가 붙어 있으면
통계 소프트웨어는 이 열 전체를 텍스트로 읽습니다.
평균을 구하거나 t-test를 돌리는 것이 불가능합니다.
원본 (텍스트로 인식) → 변환 후 (숫자)
────────────────────────────────────────
45.2 AU → 45.2
8.3 g/m²h → 8.3
12.1 μg/cm² → 12.1
문제 3 — 날짜 형식이 제각각이다
측정일
2024/3/5 ← V1 방문: 슬래시, 선행 0 없음 (한 자리 일)
2024/3/19 ← V2 방문: 슬래시, 선행 0 없음 (두 자리 일)
2024-04-02 ← V3 방문: 하이픈, 두 자리 월·일 (ISO 형식)
주의:
sample_raw.csv를 Excel로 열면 이 문제가 보이지 않습니다. Excel이 날짜를 자동으로 파싱해서 통일된 형식으로 표시하기 때문입니다. 원본 그대로 보려면 VS Code(또는 메모장)로 파일을 열어야 합니다.
같은 측정일 열인데 형식이 달라서 날짜 순서대로 정렬하거나
방문 간 경과 일수를 계산하면 오류가 발생합니다.
문제 4 — 범주형 값이 텍스트 그대로다
skin_type adverse_event
건성 / 지성 없음 / 경미 / 중등도
중성 / 복합성
R · SPSS의 코드북은 범주를 숫자로 요구합니다. 텍스트 그대로 넣으면 그룹 비교 분석에서 오류가 납니다.
skin_type adverse_event
건성→1, 중성→2, 지성→3, 복합성→4 없음→0, 경미→1, 중등도→2, 중증→3
문제 5 — 결측값이 숨어 있다
S003_박서연(F/42),V1,...,중성,,이수진 ← adverse_event 빈칸
S004_최동현(M/29),V2,...,,9.4 g/m²h,... ← moisture_val 빈칸
빈 칸이 “측정을 안 한 것” 인지 “기기 오류” 인지 “0” 인지 알 수 없습니다. 결측값을 어떻게 처리하느냐에 따라 분석 결과가 달라지므로, 먼저 어느 열에 몇 개가 있는지 파악하는 것이 첫 번째 작업입니다.
문제 6 — 열 이름이 영어·한글 혼재한다
Subject_Info moisture_val TEWL_val sebum_val skin_type adverse_event 담당연구원
영어 약어, 영어 전체 이름, 한글이 섞여 있습니다. 코드북 기준으로 통일하지 않으면 인수인계나 공동 분석에서 혼선이 생깁니다.
정리 — 6가지 문제와 필요한 처리
| # | 문제 | 열 | 필요한 처리 |
|---|---|---|---|
| 1 | 여러 정보가 한 열에 뭉침 | Subject_Info |
4개 열로 분리 |
| 2 | 숫자에 단위가 붙어있음 | moisture_val TEWL_val sebum_val |
숫자만 추출 |
| 3 | 날짜 형식 혼재 | 측정일 |
형식 통일 |
| 4 | 범주가 텍스트 | skin_type adverse_event |
숫자 코드로 변환 |
| 5 | 결측값 존재 | adverse_event moisture_val |
위치 파악 후 처리 |
| 6 | 열 이름 혼재 | 전체 | 코드북 기준 통일 |
이 6가지를 해결하는 것이 Part 1 — 데이터 변환기의 목표입니다.
요건 정립 — 목표 Output 명세
문제를 파악했으면 다음 질문은 “어떤 형태로 만들어야 하는가?” 입니다. R/SPSS에 바로 넣을 수 있는 형태를 기준으로 열 이름·타입·변환 규칙을 미리 정합니다.
Before / After
[ 원본 한 행 ]
S001_김민지(F/28), V1, 2024/3/5, 45.2 AU, 8.3 g/m²h, 12.1 μg/cm², 건성, 없음, 이수진
[ 목표 한 행 ]
S001, 김민지, F, 28, V1, 2024-03-05, 45.2, 8.3, 12.1, 1, 0, 이수진
열별 변환 명세
| 목표 열 이름 | 원본 열 | 변환 내용 | 타입 |
|---|---|---|---|
피험자번호 |
Subject_Info |
S001_김민지(F/28) → S001 |
텍스트 |
피험자명 |
Subject_Info |
→ 김민지 |
텍스트 |
성별 |
Subject_Info |
→ F |
텍스트 |
나이 |
Subject_Info |
→ 28 |
숫자 |
방문차수 |
Visit |
V1 → 1 (숫자 추출) |
숫자 |
측정일 |
측정일 |
2024/3/5 → 2024-03-05 |
날짜 |
수분량_AU |
moisture_val |
45.2 AU → 45.2 |
숫자 |
TEWL_gm2h |
TEWL_val |
8.3 g/m²h → 8.3 |
숫자 |
피지량_ugcm2 |
sebum_val |
12.1 μg/cm² → 12.1 |
숫자 |
피부타입 |
skin_type |
건성→1, 중성→2, 지성→3, 복합성→4 | 숫자 |
이상반응 |
adverse_event |
없음→0, 경미→1, 중등도→2, 중증→3 | 숫자 |
연구원 |
담당연구원 |
열 이름만 변경 | 텍스트 |
이 명세가 앱 구현의 기준이 됩니다. AI에게 기능을 요청할 때도 이 표를 참고해서 구체적으로 설명할수록 정확한 결과를 얻습니다.
정규식 레퍼런스
sample_raw.csv의 문제들을 해결하는 핵심 도구입니다. 기호 몇 개의 조합이 전부입니다 — 순서대로 따라오면 됩니다.
정규식이란?
정규식(Regular Expression, regex) = 문자열에서 원하는 패턴을 찾아내는 규칙 언어입니다.
S001_김민지(F/28)처럼 여러 정보가 뭉쳐있는 값에서 김민지만 꺼내거나,
45.2 AU에서 45.2만 남기는 작업을 짧은 패턴 한 줄로 처리합니다.
| 방법 | 코드 | |
|---|---|---|
| Excel | 함수 여러 개 조합 | =MID(A1, FIND("_",A1)+1, FIND("(",A1)-FIND("_",A1)-1) |
| 정규식 | 패턴 한 줄 | _([^(]+)\( |
둘 다 S001_김민지(F/28)에서 김민지를 꺼내는 작업입니다.
정규식은 추출뿐 아니라 치환(값 일부 바꾸기), 분리(열 나누기)에도 쓰입니다.
이 세 가지 작업이 sample_raw.csv의 문제를 해결하는 핵심 수단입니다.
노코드 관점에서 — 정규식을 직접 외울 필요는 없습니다. “어떤 패턴이 필요한지”를 말로 설명하면 AI가 패턴을 대신 작성해줍니다. 아래 내용은 AI에게 정확하게 요청하거나, AI가 만들어준 패턴을 이해하기 위한 참고 자료입니다.
두 가지 모드
추출 (Extract) — 원하는 부분만 꺼내기
값 전체가 아닌 일부만 뽑아야 할 때 사용합니다.
45.2 AU → 45.2 (숫자만)
S001_김민지(F/28) → S001 (ID만)
S001_김민지(F/28) → 김민지 (이름만)
S001_김민지(F/28) → F (성별만)
V1_S22 → 1 과 22 (방문차수, 피험자번호 동시에)
치환 (Replace) — 원하는 부분을 지우거나 바꾸기
값 일부를 제거하거나 다른 값으로 교체할 때 사용합니다.
45.2 AU → " AU" 제거 → 45.2
8.3 g/m²h → " g/m²h" 제거 → 8.3
2024/3/5 → "/" → "-" → 2024-3-5
기호 상세 레퍼런스 — AI가 만든 패턴을 읽고 싶을 때 펼치기
게임처럼 정규식을 익히고 싶다면 → regexlearn.com
기호 하나씩 익히기
복잡해 보이는 정규식도 기호 몇 개의 조합입니다. 가장 쉬운 것부터 하나씩 쌓아봅니다.
패턴이 문자열에 작동하는 방식
정규식 패턴은 “이런 생김새의 글자를 찾아줘”라는 주문서입니다.
패턴을 실행하면 문자열을 왼쪽부터 한 글자씩 훑으며 주문과 일치하는 자리를 찾습니다. 일치하는 자리를 매칭(matching) 이라고 하고, 매칭된 자리의 값을 꺼내는 것을 추출(extract) 이라고 합니다.
문자열: V 1 _ S 2 2
↓ ↓ ↓ ↓ ↓ ↓
패턴 검사: 여기? 여기? 여기? ... ← 왼쪽부터 한 자리씩 확인
패턴이 “어디에 매칭됐는가” + “그 자리의 값이 무엇인가” — 이 두 가지가 정규식의 전부입니다.
\d — 숫자 한 글자
\d는 “숫자 한 글자짜리를 찾아줘” 라는 주문입니다.
d는 digit(숫자)의 첫 글자입니다. 0~9 중 아무 숫자 하나에 매칭됩니다.
V1_S22에 \d를 적용하면:
V 1 _ S 2 2
↓ ↓ ↓ ↓ ↓ ↓
✗ ✓ ✗ ✗ ✓ ✓
1 2 2 ← 매칭된 값 (각각 따로)
\d는 한 글자짜리 주문이므로, 붙어있는 22도 2와 2 두 개로 따로 매칭됩니다.
패턴: \d
입력: V1_S22
매칭: V[1]_S[2][2]
같은 패턴이라도 추출하느냐 치환하느냐에 따라 결과가 달라집니다.
| 작업 | 의미 | 결과 |
|---|---|---|
| 추출 (Extract) | 매칭된 값을 꺼내 새 열로 만들기 | '1', '2', '2' (3개) |
| 치환 (Replace) | 매칭된 자리를 다른 값으로 덮어쓰기 | V*_S** (숫자를 *로 교체한 예) |
숫자 여러 자리를 하나로 묶으려면
\d+가 필요합니다. (바로 아래에서 설명)
\d+ — 연속된 숫자 전체
+는 “앞의 것이 1번 이상 반복”입니다. \d+는 연속된 숫자 전체를 하나로 묶어 잡습니다.
패턴: \d+
입력: V1_S22
매칭: V[1]_S[22] ← 22가 하나로 묶임
패턴: \d+
입력: Subject_003
매칭: Subject_[003] ← 자릿수 상관없이 한 번에
| 작업 | \d 결과 |
\d+ 결과 |
|---|---|---|
추출 (V1_S22) |
'1', '2', '2' (3개) |
'1', '22' (2개) |
[ ] — 목록 중 하나 / [\d.] — 숫자 또는 점
[ ] 안에 나열한 문자들 중 딱 하나에 매칭됩니다.
패턴: [\d.]
입력: 45.2 AU
4 5 . 2 ← 각각 숫자 또는 점 → 매칭
A U ← 숫자도 점도 아님 → 매칭 안 됨
매칭: [4][5][.][2] AU ← 한 글자씩 따로
+를 붙이면 연속된 부분을 하나로 묶습니다.
패턴: [\d.]+
입력: 45.2 AU
매칭: [45.2] AU ← 소수점 포함 숫자를 통째로
() — 매칭된 부분 중 꺼낼 범위 지정
[\d.]+은 45.2에 매칭되지만, 그 값을 결과로 꺼내려면 ()로 감싸야 합니다.
| 패턴 | 하는 일 | 입력: 45.2 AU 결과 |
|---|---|---|
[\d.] |
한 글자씩 매칭 | '4', '5', '.', '2' |
[\d.]+ |
연속 구간을 묶어 매칭 | '45.2' (매칭은 됐지만 추출 안 됨) |
([\d.]+) |
연속 구간을 묶어 추출 | '45.2' ← 이 값이 새 열에 들어감 |
패턴: ([\d.]+)
입력: 45.2 AU → 추출: 45.2
입력: 8.3 g/m²h → 추출: 8.3
입력: 12.1 μg/cm² → 추출: 12.1
\( \) — 문자 그대로의 괄호를 찾을 때
()는 방금 배운 것처럼 정규식에서 “추출 범위”라는 특별한 의미로 이미 쓰이고 있습니다.
그래서 데이터 안의 실제 괄호 문자 (를 찾으려면, 앞에 \를 붙여서
“이건 정규식 기호가 아니라 진짜 괄호 문자야” 라고 표시해야 합니다.
패턴: ( ← 정규식이 "추출 범위 시작"으로 해석 → 오류
패턴: \( ← "진짜 괄호 문자 ( 를 찾아줘" → 정상 동작
패턴: \(([MF])
입력: 김민지(F/28)
\( ← 여는 괄호 ( 를 찾아 위치 확인
([MF]) ← 바로 뒤 M 또는 F를 추출
결과: F
^ — 문자열의 시작 / $ — 문자열의 끝
위치를 고정합니다. 같은 문자가 여러 곳에 있을 때 앞/뒤를 특정할 수 있습니다.
패턴: ^([^_]+) ← 시작부터 _ 전까지
입력: S001_김민지(F/28)
결과: S001
패턴: \s*AU$ ← 끝에 붙은 " AU" 제거
입력: 45.2 AU
치환후: 45.2
패턴: \s*g/m²h$
입력: 8.3 g/m²h
치환후: 8.3
[^_] — 특정 문자를 제외한 모든 것
[ ] 안에서 ^는 “이것 빼고 전부”입니다.
패턴: [^_]+
입력: S001_김민지
매칭: [S001]_김민지 ← _ 가 나오기 전까지
패턴: [^(]+
입력: 김민지(F/28)
매칭: [김민지](F/28) ← ( 가 나오기 전까지
\s — 공백 문자
스페이스, 탭 등 눈에 보이지 않는 공백을 잡습니다.
\s*는 “공백이 없어도 매칭”이어서 공백 유무와 상관없이 단위를 제거할 때 씁니다.
패턴: \s*AU$
입력: 45.2 AU ← 공백 있음 → 매칭
입력: 45.2AU ← 공백 없음 → 역시 매칭 (\s*는 0번도 OK)
sample_raw.csv 패턴 완전 해석
위 기호들로 실제 데이터 패턴을 처음부터 읽어봅니다.
S001_김민지(F/28) — 4개 정보 분리
S001 _ 김민지 ( F / 28 )
│ │ │ │
│ │ │ └── 나이
│ │ └───────── 성별
│ └───────────────── 이름
└─────────────────────────── ID
| 추출 목표 | 패턴 | 읽는 법 | 결과 |
|---|---|---|---|
| ID | ^([^_]+) |
시작(^)부터 _ 제외([^_]) 연속(+) 추출(()) |
S001 |
| 이름 | _([^(]+)\( |
_ 뒤부터 ( 제외([^(]) 연속, ( 앞에서 멈춤 |
김민지 |
| 성별 | \(([MF]) |
여는괄호(\() 바로 뒤 M 또는 F 추출 |
F |
| 나이 | /(\d+)\) |
/ 뒤 숫자들, 닫는괄호(\)) 앞에서 멈춤 |
28 |
45.2 AU / 8.3 g/m²h / 12.1 μg/cm² — 숫자만 추출
단위 형태가 달라도 패턴 하나로 전부 처리됩니다.
패턴: ([\d.]+)
45.2 AU → 45.2
8.3 g/m²h → 8.3
12.1 μg/cm² → 12.1
0.5 mg/dL → 0.5 ← 다른 단위도 동일하게 적용
V1_S22 — 방문차수와 피험자번호 동시 추출
패턴: V(\d+)_S(\d+)
│ │ │ │
│ │ │ └── 피험자 번호 추출 → 22
│ │ └───── 구분자 _S (고정 문자)
│ └───────── 방문 차수 추출 → 1
└──────────── V (고정 문자)
V1_S22 → 방문차수: 1, 피험자: 22
V2_S5 → 방문차수: 2, 피험자: 5
V3_S103 → 방문차수: 3, 피험자: 103
기호 요약
| 기호 | 의미 | 기억법 |
|---|---|---|
\d |
숫자 한 글자 | digit |
+ |
앞 패턴 1회 이상 반복 | 1번은 꼭 있어야 |
* |
앞 패턴 0회 이상 반복 | 없어도 괜찮아 |
[ ] |
목록 중 하나 | [\d.] = 숫자 또는 점 |
[^ ] |
목록 제외 전부 | [^_] = _ 빼고 다 |
() |
이 안을 결과로 추출 | 캡처 그룹 |
\( \) |
괄호 문자 자체 | \ = 이스케이프 |
^ |
문자열 시작 위치 | 앞에 고정 |
$ |
문자열 끝 위치 | 뒤에 고정 |
\s |
공백 문자 | space |
Part 1 — 데이터 변환기 만들기
app.py는 완성된 레퍼런스입니다. 지금부터 이 앱을 처음부터 직접 만들어봅니다.
작업 방식
실제 파일은 app.py 하나로 계속 수정합니다.
기능이 완성될 때마다 git 커밋으로 버전 이력을 남깁니다.
# 기능 완성 후 매번 실행
git add app.py
git commit -m "v1: 파일 업로드 및 미리보기"
파일을 app_v1.py, app_v2.py… 로 나누지 않는 이유 실무에서도 하나의 파일을 계속 수정합니다. git 커밋이 각 버전의 스냅샷 역할을 하므로, 언제든 이전 상태로 돌아갈 수 있습니다.
git log로 커밋 이력을 보면 각 단계가 기록으로 남습니다.
레퍼런스 먼저 확인
만들 앱이 어떤 모습인지 먼저 눈으로 확인합니다.
cd tutorials/week04-pandas
streamlit run app.py
프롬프트 작성 팁
AI에게 데이터 처리를 요청할 때 이 3가지를 포함하면 정확한 결과를 얻습니다:
| 요소 | 예시 |
|---|---|
| 데이터 구조 | “Subject_Info 열에 S001_김민지(F/28) 형태의 값이 있어” |
| 원하는 결과 | “피험자번호, 피험자명, 성별, 나이를 각각 별도 열로 분리하고 싶어” |
| 제약 조건 | “CSV는 utf-8-sig 인코딩으로 읽어야 해” |
❌ 나쁜 예: "데이터 처리해줘"
⭕ 좋은 예: "Subject_Info 열의 S001_김민지(F/28) 패턴에서
피험자번호(S001), 이름(김민지), 성별(F), 나이(28)를
정규식으로 분리해서 각각 새 열로 만들어줘"
모르는 용어는 괜찮습니다. 바이브코딩에서 중요한 건 정확한 용어가 아니라, 원하는 결과를 묘사하는 능력입니다.
단계별 구현
| 버전 | 추가할 기능 | git 커밋 메시지 |
|---|---|---|
| v1 | CSV / Excel 업로드 + 원본 미리보기 | v1: 파일 업로드 및 미리보기 |
| v2 | 전체 변환 기능 + 파일 저장 | v2: 변환 기능 및 저장 |
| final | 원본 · 수정본 나란히 비교 + 파일명 지정 저장 | final: 원본 비교 및 파일명 지정 저장 |
v1 — 파일 업로드 + 원본 미리보기
tutorials/week04-pandas/ 폴더에 app.py를 만들어줘.
기능:
- CSV 또는 Excel 파일을 업로드할 수 있는 Streamlit 앱
- 업로드한 파일을 읽어서 미리보기 테이블로 표시
- 파일 기본 정보도 표시 (행 수, 열 수, 열 이름 목록, 열별 결측값 개수)
조건:
- 파일이 업로드되기 전에는 안내 메시지 표시
- CSV는 utf-8-sig 인코딩으로 읽기
- 테스트용 샘플 파일 위치: tutorials/week04-pandas/sample_raw.csv
git add app.py
git commit -m "v1: 파일 업로드 및 미리보기"
v2 — 전체 변환 기능 + 파일 저장
app.py에 다음 변환 기능들과 저장 기능을 추가해줘.
변환 기능:
1. 열 분리 — Subject_Info 열을 4개로 분리
- S001_김민지(F/28) → 피험자번호(S001), 피험자명(김민지), 성별(F), 나이(28)
- 각 열 이름을 직접 입력할 수 있는 텍스트박스 4개 제공
2. 단위 제거 — 측정값에서 숫자만 추출
- moisture_val: "45.2 AU" → 45.2 / 열 이름 → 수분량_AU
- TEWL_val: "8.3 g/m²h" → 8.3 / 열 이름 → TEWL_gm2h
- sebum_val: "12.1 μg/cm²" → 12.1 / 열 이름 → 피지량_ugcm2
3. 값 매핑 — 텍스트를 숫자 코드로 변환
- skin_type → 피부타입: 건성→1, 중성→2, 지성→3, 복합성→4
- adverse_event → 이상반응: 없음→0, 경미→1, 중등도→2, 중증→3
4. 나머지 열 이름 및 형식 변환
- Visit → 방문차수: V1 → 1 (숫자 추출)
- 측정일: 날짜 형식 통일 (YYYY-MM-DD)
- 담당연구원 → 연구원
저장 기능:
- 변환된 수정본을 CSV 또는 Excel로 다운로드할 수 있는 버튼
- 파일명은 "수정본"으로 고정 (예: 수정본.csv, 수정본.xlsx)
- CSV: utf-8-sig 인코딩으로 저장
- Excel: .xlsx 형식으로 저장, openpyxl 엔진 사용
조건:
- 각 변환은 체크박스로 켜고 끌 수 있게
- 변환 후 미리보기 테이블에 즉시 반영
- 파일을 처음 업로드했을 때의 원본 데이터는 절대 바뀌면 안 됨
변환 기능들은 원본을 복사한 별도 수정본에만 적용해줘
Streamlit은 UI를 조작할 때마다 스크립트가 전체 재실행되는데,
그때도 원본과 수정본이 사라지지 않아야 해
git add app.py
git commit -m "v2: 변환 기능 및 저장"
final — 원본 · 수정본 비교 + 파일명 지정 저장
app.py를 다음과 같이 개선해줘.
1. 원본 · 수정본 나란히 보기
- 화면을 좌우 2열로 나눠서 왼쪽은 원본, 오른쪽은 수정본 표시
- 수정본은 변환 체크박스를 켜고 끄면 실시간으로 반영
2. 파일명 지정 저장
- 다운로드 전에 파일명을 직접 입력할 수 있는 텍스트박스 추가
- 기본값은 "수정본"
- CSV / Excel 선택 후 해당 파일명으로 다운로드
git add app.py
git commit -m "final: 원본 비교 및 파일명 지정 저장"
Part 2 — 분석 파이프라인 (app_analysis.py)
streamlit run app_analysis.py
Part 1이 범용 변환기라면, Part 2는 장비 Raw Data → 취합 → 통계 → 시각화까지 하나의 흐름으로 이어지는 분석 파이프라인입니다.
Streamlit + pandas로 이런 것도 된다는 걸 보여주는 확장 예제입니다.
다루는 데이터
피험자 약 30명이 특정 시료를 4주간 섭취하고, 섭취 전(V1) / 섭취 후(V2) 피부 지질 성분을 측정한 데이터입니다.
| 파일 | 성분 | 시트 수 |
|---|---|---|
Cicca B5_cholesterol_raw data.xlsx |
콜레스테롤 | 1개 |
Cicca B5_fatty Acid_raw data.xlsx |
지방산 | 6개 (C20FA ~ C30FA) |
두 파일 모두 장비(LC-MS 등)가 출력한 동일한 구조입니다:
Row 1~6: 장비 메타데이터 (출력일시, 장비명 등) → 건너뛰기
Row 7: 컬럼 헤더
Column C = "Sample Text" → V1_S1, V2_S33 형식
Column L = "ng/mg protein" → 실제 측정값
Row 8~: 데이터
전체 흐름
[ 입력 ]
파일 A (1 sheet) 파일 B (N sheets)
↓ ↓
[ Step 1: 미리보기 ]
raw 상태 10행 확인
→ skiprows 숫자 입력
→ 정제된 테이블 확인
↓ ↓
[ Step 2: 추출 ]
필요한 열 선택 (Sample Text, 측정값)
정규식으로 V1_S12 → 방문차수 / 피험자번호 분리
↓ ↓
[ Step 3: 취합 ]
파일 A 1개 시트 + 파일 B N개 시트
→ 하나의 DataFrame으로 세로 합치기 (Tidy Data)
↓
[ Step 4: 통계 · 시각화 ]
Z-score 계산 + Violin Plot / Paired Plot
↓
[ Step 5: 저장 ]
통합 정리 파일 다운로드 (CSV / Excel)
Step 1: 파일 업로드 + 미리보기
파일을 업로드하면 raw 상태(처리 전)를 먼저 보여줍니다.
장비 메타데이터가 상단에 몇 행 있는지 눈으로 확인하고, 건너뛸 행 수를 직접 입력합니다.
건너뛸 행 수: [6] ← 숫자 바꿀 때마다 미리보기 즉시 반영
파일 A(1 sheet)와 파일 B(N sheets)를 각각 업로드합니다. 파일 B는 시트 드롭다운으로 시트별 미리보기를 확인할 수 있습니다.
프롬프트:
tutorials/week04-pandas/ 폴더에 app_analysis.py를 만들어줘.
기능:
- Excel 파일 2개를 업로드할 수 있는 Streamlit 앱
- 업로드한 파일을 raw 상태(처음 10행)로 먼저 표시
- "건너뛸 행 수" 숫자 입력 → skiprows 적용 후 정제된 미리보기 표시
- 여러 시트가 있는 파일은 시트 선택 드롭다운 제공
조건:
- 파일이 업로드되기 전에는 안내 메시지 표시
- Excel: openpyxl 엔진 사용
git add app_analysis.py
git commit -m "v1: 파일 업로드 및 미리보기"
Step 2: 단일 시트 처리 + 저장
하나의 시트에서 패턴을 완성하는 단계입니다. 이 패턴이 Step 3에서 N개 시트로 그대로 확장됩니다.
열 선택
피험자 정보 열과 측정값 열을 드롭다운으로 선택합니다. 컬럼명을 하드코딩하지 않고 선택하는 방식이라 다른 파일에도 재사용할 수 있습니다.
정규식으로 피험자 정보 분리
선택한 열에서 V1_S12 패턴을 분리합니다:
V(\d+)_S(\d+)
│ │ │ │
│ │ │ └─ 숫자 1개 이상 → 피험자 번호 (12)
│ │ └──── 구분자: 언더스코어
│ └───────── 숫자 1개 이상 → 방문 차수 (1)
└──────────── V 글자 자체
방문차수별 라벨은 텍스트박스로 직접 입력 (기본값: 1 → 섭취전, 2 → 4주 섭취 후)
목표 구조
V | S | 측정값
섭취전 | 1 | 0.644
섭취전 | 2 | 0.512
4주 섭취 후 | 1 | 0.891
...
한 행 = 피험자 1명 × 방문 1회. 중복 없음.
프롬프트:
app_analysis.py에 데이터 추출 기능을 추가해줘.
레이아웃:
- 사이드바에 모든 설정 컨트롤 배치:
- 파일 업로드
- 건너뛸 행 수 (skiprows) 숫자 입력
- 시트 선택 드롭다운
- 피험자 정보 열 선택 드롭다운 (V_S 패턴)
- 측정값 열 선택 드롭다운
- V1 라벨 텍스트박스 (기본값: "섭취전")
- V2 라벨 텍스트박스 (기본값: "4주 섭취 후")
- 메인 화면에는 미리보기와 결과만 표시
기능:
- 선택한 피험자 정보 열에서 정규식 V(\d+)_S(\d+)로 방문차수(V), 피험자번호(S) 분리
- 결과 미리보기 표시 (열: V, S, 측정값)
- "저장" 버튼 클릭 시 원본파일명_selected.xlsx 로 저장
- 시트 이름 = 원본 시트 이름 유지
- Excel: openpyxl 엔진 사용
- 한 행 = 피험자 1명 × 방문 1회, 중복 없이
git add app_analysis.py
git commit -m "v2: 단일 시트 추출 및 저장"
Step 3: 다중 시트 가로 병합 + 단일 시트 저장
Fatty Acid처럼 여러 시트가 담긴 파일에서 각 시트의 측정값을 열로 가로 병합해 하나의 시트로 만드는 단계입니다.
C20FA 시트: V, S, 값 ─┐
C22FA 시트: V, S, 값 ├─ V + S 기준 가로 병합 → FA_total 합계 열 추가
C24FA 시트: V, S, 값 │
... ─┘
↓
결과종합_지방산 (시트 1장)
복용기간 | 피험자번호 | C20FA | C22FA | C24FA | C26FA | C28FA | C30FA | FA_total
섭취전 | 1 | 191.1 | 252.6 | ... | ... | ... | ... | 3159.85
섭취전 | 2 | 129.8 | 230.2 | ... | ... | ... | ... | 2601.23
4주 섭취 후 | 1 | 591.8 | 806.1 | ... | ... | ... | ... | 8979.22
각 시트 이름이 열 이름이 됩니다. FA_total은 전체 성분 열의 합계입니다.
프롬프트:
app_analysis.py에 다중 시트 가로 병합 기능을 추가해줘.
레이아웃:
- 사이드바에 모든 설정 컨트롤 배치:
- 파일 업로드 (복수 파일 가능)
- 건너뛸 행 수 (skiprows) 숫자 입력
- 피험자 정보 열 선택 드롭다운 (V_S 패턴, 모든 파일에 동일 적용)
- 측정값 열 선택 드롭다운 (모든 파일에 동일 적용)
- V1 라벨 텍스트박스 (기본값: "섭취전")
- V2 라벨 텍스트박스 (기본값: "4주 섭취 후")
- 최종 취합 파일명 텍스트박스
- "데이터 병합 시작" 버튼
- 저장 시 시트 이름은 업로드한 파일명을 그대로 사용 (별도 입력 없음)
- 메인 화면에는 미리보기와 결과만 표시
처리 방식:
- 각 시트에서 V, S, 측정값 3열 추출
- 측정값 열 이름을 해당 시트 이름으로 변경
- 모든 시트를 V + S 기준으로 가로 병합 (merge)
- Total 열 추가 = 측정값 열 전체 합계
- 결과 미리보기 표시 후 다운로드 버튼 표시
- Excel: openpyxl 엔진 사용
git add app_analysis.py
git commit -m "v3: 다중 시트 가로 병합 및 저장"
Step 4: 통계 · 시각화
취합된 파일(output_merged.xlsx)을 불러와 탭 3개를 순서대로 추가합니다. 프롬프트를 하나씩 넣으면 탭이 하나씩 쌓입니다.
참고 — Streamlit 시각화 라이브러리
plotly: 인터랙티브 (확대·클릭·hover). Streamlit과 궁합이 좋아 추천.matplotlib: 정적 이미지. 논문용 그림에 적합. 차트 종류가 떠오르지 않을 때는 “이런 걸 보고 싶다”고 설명하면 AI가 제안합니다.
탭 1 — 📊 통계 요약
app_analysis.py에 "📊 통계 요약" 탭을 추가해줘.
데이터 구조:
- 분석 대상 파일은 여러 시트를 포함한 xlsx (예: output_merged.xlsx)
- 각 시트의 열 구성: Visit(방문 구분), Sample #(피험자번호), 측정값 열 1개 이상, Total
- Visit 열의 값은 2종류 (예: 섭취전 / 4주 섭취 후)
사이드바에 추가:
- xlsx 파일 업로드
- 분석할 시트 선택 드롭다운 (파일 내 전체 시트 목록 표시)
→ 선택한 시트 기준으로 아래 통계 전체가 갱신됨
탭에 표시할 내용:
- 현재 선택된 시트명 표시
- 측정값 열 및 Total 열에 대해 Visit별 평균, 중앙값, 표준편차를 표로 표시
- 피험자별 변화량·변화율 계산 (Sample # 기준 섭취전·섭취후 매칭):
- 변화량 = 섭취후 - 섭취전
- 변화율(%) = (섭취후 - 섭취전) / 섭취전 × 100
- 변화량·변화율 요약 통계(평균, 중앙값, 표준편차)도 표로 표시
조건:
- 측정값 열은 Visit, Sample #, Total을 제외한 나머지 열로 자동 감지
- 측정값이 1개인 경우도 동일하게 동작
git add app_analysis.py
git commit -m "v4-1: 통계 요약 탭"
탭 2 — 📈 개인별 변화
app_analysis.py에 "📈 개인별 변화" 탭을 추가해줘.
데이터 구조:
- 탭 1과 동일한 파일(여러 시트 포함 xlsx) 사용
- 탭 1 사이드바의 시트 선택과 연동 — 같은 시트를 바라봄
사이드바에 추가:
- 시각화할 열 선택 드롭다운
(현재 선택된 시트의 측정값 열 + Total 목록에서 선택)
→ 시트가 바뀌면 열 목록도 자동 갱신
차트: Paired Strip Plot (plotly 사용)
- 가로축: Visit (섭취전 / 섭취후)
- 세로축: 선택한 열의 측정값
- 같은 피험자(Sample #)의 섭취전·섭취후 점을 선으로 연결
- 점 1개 = 피험자 1명, 선이 올라가면 증가·내려가면 감소
- 평균값을 굵은 선 또는 다른 색으로 오버레이
- 차트 제목에 현재 선택된 시트명과 열 이름 표시
조건:
- plotly express 사용
- 피험자 선 색상은 단일 색상으로 통일
git add app_analysis.py
git commit -m "v4-2: 개인별 변화 탭"
탭 3 — 📉 성분 비교
app_analysis.py에 "📉 성분 비교" 탭을 추가해줘.
차트: 평균 변화율 Bar Chart (plotly 사용)
- 가로축: 측정값 열 이름 (성분명)
- 세로축: 평균 변화율(%) = (섭취후 평균 - 섭취전 평균) / 섭취전 평균 × 100
- Total 열은 제외
- 양수(증가)는 파란색, 음수(감소)는 빨간색으로 구분
- 각 막대 위에 변화율(%) 수치 표시
조건:
- 측정값이 1개인 경우 막대 1개만 표시 (에러 없이 동작)
- plotly express 사용
git add app_analysis.py
git commit -m "v4-3: 성분 비교 탭"
Step 5: 결과 저장
Week 4 Summary
Part 1 — 데이터 변환기 (app.py)
지저분한 측정 파일을 R/SPSS에 바로 넣을 수 있는 형태로 바꾸는 앱을 처음부터 직접 만들었습니다.
| 단계 | 한 일 |
|---|---|
| v1 | CSV/Excel 업로드 + 원본 미리보기 |
| v2 | 정규식 열 분리·단위 제거·값 매핑·열 이름 정리 + 저장 |
| final | 원본·수정본 나란히 비교 + 파일명 지정 저장 |
핵심 개념: 정규식으로 뭉쳐있는 값을 쪼개고, 텍스트를 숫자 코드로 바꾸고, 결과를 파일로 내보내는 흐름.
Part 2 — 분석 파이프라인 (app_analysis.py)
장비가 출력한 Raw 파일에서 분석 가능한 형태까지 전 과정을 앱 하나로 처리했습니다.
장비 Raw 파일 (Excel)
↓ skiprows + 열 선택 + 정규식 분리 (Step 1·2)
원본파일명_selected.xlsx (Visit, Sample #, 측정값)
↓ 여러 파일 가로 병합 + Total 합계 (Step 3)
취합본.xlsx (Visit, Sample # + 성분별 열 + Total)
↓ 시트 선택 → 통계 + 시각화 (Step 4)
변화량·변화율 요약 / Paired Strip Plot / 성분별 변화율 Bar
핵심 개념: 파일 구조가 달라도 열 선택·skiprows·정규식 조합으로 범용 처리. 여러 시트를 가로로 병합해 하나의 분석 테이블로 만드는 흐름.
바이브코딩으로 배운 것
pandas와 Streamlit을 직접 외우지 않아도, “어떤 문제인지” 와 “어떤 결과를 원하는지” 를 말로 표현할 수 있으면 AI가 코드를 만들어줍니다.
이번 주에 만든 두 앱이 그 증거입니다.
과제
본인이 실제로 쓰는 Excel 파일을 업로드해서 AI에게 이렇게 요청해보세요:
첨부한 파일에서 [열 이름] 열이 "[예시값]" 형태인데,
정규식으로 [원하는 부분]만 추출하는 기능을 앱에 추가해줘.