Spaces:
Running
Running
Update src/streamlit_app.py
Browse files- src/streamlit_app.py +86 -80
src/streamlit_app.py
CHANGED
@@ -6,91 +6,97 @@ import seaborn as sns
|
|
6 |
from scipy.stats import norm, skew
|
7 |
import platform
|
8 |
|
9 |
-
# 한글 폰트 설정 (
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
#
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
plt.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
|
21 |
def main():
|
22 |
-
|
23 |
-
스트림릿을 이용한 학생 점수 분포 분석 애플리케이션
|
24 |
-
"""
|
25 |
st.title("학생 점수 분포 분석 도구 📊")
|
26 |
-
st.write("CSV 파일을
|
27 |
st.write("---")
|
28 |
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
st.write("#### 🎨 점수 분포 시각화")
|
56 |
-
fig, ax = plt.subplots(figsize=(10, 6))
|
57 |
-
|
58 |
-
# 히스토그램 및 KDE 플롯
|
59 |
-
sns.histplot(scores, kde=True, stat='density', label='학생 점수 분포', ax=ax)
|
60 |
-
|
61 |
-
# 정규분포 곡선 추가
|
62 |
-
mu, std = norm.fit(scores)
|
63 |
-
xmin, xmax = plt.xlim()
|
64 |
-
x = np.linspace(xmin, xmax, 100)
|
65 |
-
p = norm.pdf(x, mu, std)
|
66 |
-
ax.plot(x, p, 'k', linewidth=2, label='정규분포 곡선')
|
67 |
-
|
68 |
-
title = f"'{score_column}' 점수 분포 (평균: {mu:.2f}, 표준편차: {std:.2f})"
|
69 |
-
ax.set_title(title)
|
70 |
-
ax.set_xlabel('점수')
|
71 |
-
ax.set_ylabel('밀도')
|
72 |
-
ax.legend()
|
73 |
-
st.pyplot(fig)
|
74 |
-
|
75 |
-
# 3. 왜도(Skewness) 계산 및 해석
|
76 |
-
st.write("#### 📐 왜도 (Skewness) 분석")
|
77 |
-
skewness = skew(scores)
|
78 |
-
st.metric(label="왜도 (Skewness)", value=f"{skewness:.4f}")
|
79 |
-
|
80 |
-
if skewness > 0.5:
|
81 |
-
st.info("꼬리가 오른쪽으로 긴 분포 (Positive Skew): 대부분의 학생들이 평균보다 낮은 점수에 몰려있고, 일부 학생들이 매우 높은 점수를 받았습니다.")
|
82 |
-
elif skewness < -0.5:
|
83 |
-
st.info("꼬리가 왼쪽으로 긴 분포 (Negative Skew): 대부분의 학생들이 평균보다 높은 점수에 몰려있고, 일부 학생들이 매우 낮은 점수를 받았습니다.")
|
84 |
-
else:
|
85 |
-
st.info("대칭에 가까운 분포: 점수가 평균을 중심으로 비교적 고르게 분포되어 있습니다.")
|
86 |
-
|
87 |
-
else:
|
88 |
-
st.error(f"오류: 선택하신 '{score_column}' 열은 숫자 데이터가 아닙니다. 숫자 데이터로 구성된 열을 선택해주세요.")
|
89 |
-
|
90 |
-
except Exception as e:
|
91 |
-
st.error(f"파일을 읽는 도중 오류가 발생했습니다: {e}")
|
92 |
-
st.warning("CSV 파일이 'utf-8-sig' 또는 'utf-8' 인코딩 형식인지 확인해주세요.")
|
93 |
-
|
94 |
|
95 |
if __name__ == '__main__':
|
96 |
main()
|
|
|
6 |
from scipy.stats import norm, skew
|
7 |
import platform
|
8 |
|
9 |
+
# 한글 폰트 설정 (다양한 OS 환경 지원)
|
10 |
+
def set_korean_font():
|
11 |
+
if platform.system() == 'Windows':
|
12 |
+
plt.rc('font', family='Malgun Gothic')
|
13 |
+
elif platform.system() == 'Darwin': # Mac
|
14 |
+
plt.rc('font', family='AppleGothic')
|
15 |
+
else: # Linux
|
16 |
+
# 나눔고딕 폰트가 설치되어 있어야 합니다.
|
17 |
+
# 터미널에서 `sudo apt-get install -y fonts-nanum*` 실행
|
18 |
+
try:
|
19 |
+
plt.rc('font', family='NanumGothic')
|
20 |
+
except:
|
21 |
+
st.warning("나눔고딕 폰트가 설치되어 있지 않아 한글이 깨질 수 있습니다. 'sudo apt-get install -y fonts-nanum*' 명령어로 폰트를 설치해주세요.")
|
22 |
+
plt.rcParams['axes.unicode_minus'] = False # 마이너스 폰트 깨짐 방지
|
23 |
+
|
24 |
+
def analyze_scores(df):
|
25 |
+
"""데이터프레임을 받아 분석 결과를 표시하는 함수"""
|
26 |
+
st.subheader("데이터 미리보기 (상위 5개)")
|
27 |
+
st.dataframe(df.head())
|
28 |
+
|
29 |
+
# 분석할 점수 열 선택
|
30 |
+
score_column = st.selectbox("분석할 점수 열(column)을 선택하세요:", df.columns)
|
31 |
+
|
32 |
+
if score_column:
|
33 |
+
scores = df[score_column].dropna()
|
34 |
+
|
35 |
+
if pd.api.types.is_numeric_dtype(scores):
|
36 |
+
st.subheader(f"'{score_column}' 점수 분포 분석 결과")
|
37 |
+
|
38 |
+
# 1. 기술 통계량
|
39 |
+
st.write("#### 📈 기술 통계량")
|
40 |
+
st.table(scores.describe())
|
41 |
+
|
42 |
+
# 2. 분포 시각화
|
43 |
+
st.write("#### 🎨 점수 분포 시각화")
|
44 |
+
fig, ax = plt.subplots(figsize=(10, 6))
|
45 |
+
sns.histplot(scores, kde=True, stat='density', label='학생 점수 분포', ax=ax)
|
46 |
+
mu, std = norm.fit(scores)
|
47 |
+
xmin, xmax = plt.xlim()
|
48 |
+
x = np.linspace(xmin, xmax, 100)
|
49 |
+
p = norm.pdf(x, mu, std)
|
50 |
+
ax.plot(x, p, 'k', linewidth=2, label='정규분포 곡선')
|
51 |
+
ax.set_title(f"'{score_column}' 점수 분포 (평균: {mu:.2f}, 표준편차: {std:.2f})")
|
52 |
+
ax.set_xlabel('점수'); ax.set_ylabel('밀도'); ax.legend()
|
53 |
+
st.pyplot(fig)
|
54 |
+
|
55 |
+
# 3. 왜도(Skewness) 분석
|
56 |
+
st.write("#### 📐 왜도 (Skewness) 분석")
|
57 |
+
skewness = skew(scores)
|
58 |
+
st.metric(label="왜도 (Skewness)", value=f"{skewness:.4f}")
|
59 |
+
if skewness > 0.5:
|
60 |
+
st.info("꼬리가 오른쪽으로 긴 분포 (Positive Skew): 대부분의 학생들이 평균보다 낮은 점수에 몰려있고, 일부 고득점자들이 평균을 높이고 있습니다.")
|
61 |
+
elif skewness < -0.5:
|
62 |
+
st.info("꼬리가 왼쪽으로 긴 분포 (Negative Skew): 대부분의 학생들이 평균보다 높은 점수에 몰려있고, 일부 저득점자들이 평균을 낮추고 있습니다.")
|
63 |
+
else:
|
64 |
+
st.info("대칭에 가까운 분포: 점수가 평균을 중심으로 비교적 고르게 분포되어 있습니다.")
|
65 |
+
else:
|
66 |
+
st.error(f"오류: 선택하신 '{score_column}' 열은 숫자 데이터가 아닙니다. 숫자 형식의 열을 선택해주세요.")
|
67 |
|
68 |
def main():
|
69 |
+
set_korean_font()
|
|
|
|
|
70 |
st.title("학생 점수 분포 분석 도구 📊")
|
71 |
+
st.write("CSV 파일을 직접 업로드하거나 Google Sheets URL을 붙여넣어 학생 점수 분포를 분석합니다.")
|
72 |
st.write("---")
|
73 |
|
74 |
+
st.sidebar.title("데이터 가져오기")
|
75 |
+
source_option = st.sidebar.radio("데이터 소스를 선택하세요:", ("Google Sheets URL", "CSV 파일 업로드"))
|
76 |
+
|
77 |
+
df = None
|
78 |
+
|
79 |
+
if source_option == "Google Sheets URL":
|
80 |
+
url = st.sidebar.text_input("웹에 게시된 Google Sheets CSV URL을 입력하세요.")
|
81 |
+
if url:
|
82 |
+
try:
|
83 |
+
df = pd.read_csv(url)
|
84 |
+
except Exception as e:
|
85 |
+
st.error(f"URL로부터 데이터를 읽는 중 오류가 발생했습니다: {e}")
|
86 |
+
st.warning("올바른 Google Sheets '웹 게시' CSV URL인지 확인해주세요.")
|
87 |
+
|
88 |
+
elif source_option == "CSV 파일 업로드":
|
89 |
+
uploaded_file = st.sidebar.file_uploader("CSV 파일을 업로드하세요.", type="csv")
|
90 |
+
if uploaded_file:
|
91 |
+
try:
|
92 |
+
df = pd.read_csv(uploaded_file, encoding='utf-8-sig')
|
93 |
+
except Exception as e:
|
94 |
+
st.error(f"파일을 읽는 중 오류가 발생했습니다: {e}")
|
95 |
+
|
96 |
+
if df is not None:
|
97 |
+
analyze_scores(df)
|
98 |
+
else:
|
99 |
+
st.info("사이드바에서 데이터 소스를 선택하고 데이터를 불러와주세요.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
100 |
|
101 |
if __name__ == '__main__':
|
102 |
main()
|