JUNGU commited on
Commit
8702770
ยท
verified ยท
1 Parent(s): 5a91a47

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +153 -52
src/streamlit_app.py CHANGED
@@ -6,30 +6,66 @@ import seaborn as sns
6
  from scipy.stats import norm, skew
7
  import platform
8
  import os
9
- import matplotlib.font_manager as fm # [์ˆ˜์ • 1] ๋ˆ„๋ฝ๋˜์—ˆ๋˜ ํฐํŠธ ๊ด€๋ฆฌ์ž ๋ชจ๋“ˆ import
10
 
11
  def set_korean_font():
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  font_filename = "NanumGaRamYeonGgoc.ttf"
13
- # ์•ฑ ๋ฃจํŠธ ๋””๋ ‰ํ„ฐ๋ฆฌ์—์„œ ํฐํŠธ ํŒŒ์ผ ์ฐพ๊ธฐ
14
  font_path = os.path.join(os.getcwd(), font_filename)
15
-
16
- if font_path and os.path.exists(font_path):
17
  try:
18
- font_prop = fm.FontProperties(fname=font_path)
19
  fm.fontManager.addfont(font_path)
20
- plt.rc('font', family=font_prop.get_name())
21
- st.sidebar.success(f"'{font_prop.get_name()}' ํฐํŠธ ๋กœ๋”ฉ ์„ฑ๊ณต!")
 
 
22
  except Exception as e:
23
- st.sidebar.error(f"ํฐํŠธ ๋กœ๋”ฉ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}")
24
- else:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  st.sidebar.warning(
26
- f"ํฐํŠธ ํŒŒ์ผ({font_filename})์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. "
27
- "์•ฑ ๋ฃจํŠธ ๋””๋ ‰ํ† ๋ฆฌ์— ์—…๋กœ๋“œํ–ˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”."
28
  )
29
-
 
30
  plt.rcParams['axes.unicode_minus'] = False
31
 
32
- # --- ์ ์ˆ˜ ๋ถ„์„ ํ•จ์ˆ˜ ---
 
 
33
  def analyze_scores(df):
34
  """๋ฐ์ดํ„ฐํ”„๋ ˆ์ž„์„ ๋ฐ›์•„ ๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ํ‘œ์‹œํ•˜๋Š” ํ•จ์ˆ˜"""
35
  st.subheader("๋ฐ์ดํ„ฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ (์ƒ์œ„ 5๊ฐœ)")
@@ -45,6 +81,12 @@ def analyze_scores(df):
45
 
46
  if score_column:
47
  scores = df[score_column].dropna()
 
 
 
 
 
 
48
  st.subheader(f"'{score_column}' ์ ์ˆ˜ ๋ถ„ํฌ ๋ถ„์„ ๊ฒฐ๊ณผ")
49
 
50
  # 1. ๊ธฐ์ˆ  ํ†ต๊ณ„๋Ÿ‰
@@ -54,66 +96,125 @@ def analyze_scores(df):
54
  # 2. ๋ถ„ํฌ ์‹œ๊ฐํ™”
55
  st.write("#### ๐ŸŽจ ์ ์ˆ˜ ๋ถ„ํฌ ์‹œ๊ฐํ™”")
56
  fig, ax = plt.subplots(figsize=(10, 6))
57
- sns.histplot(scores, kde=True, stat='density', label='ํ•™์ƒ ์ ์ˆ˜ ๋ถ„ํฌ', ax=ax)
58
- mu, std = norm.fit(scores)
59
- xmin, xmax = plt.xlim()
60
- x = np.linspace(xmin, xmax, 100)
61
- p = norm.pdf(x, mu, std)
62
- ax.plot(x, p, 'k', linewidth=2, label='์ •๊ทœ๋ถ„ํฌ ๊ณก์„ ')
63
- ax.set_title(f"'{score_column}' ์ ์ˆ˜ ๋ถ„ํฌ (ํ‰๊ท : {mu:.2f}, ํ‘œ์ค€ํŽธ์ฐจ: {std:.2f})")
64
- ax.set_xlabel('์ ์ˆ˜'); ax.set_ylabel('๋ฐ€๋„'); ax.legend()
65
- st.pyplot(fig)
 
 
 
 
 
 
 
 
66
 
67
  # 3. ์™œ๋„(Skewness) ๋ถ„์„
68
  st.write("#### ๐Ÿ“ ์™œ๋„ (Skewness) ๋ถ„์„")
69
- skewness = skew(scores)
70
- st.metric(label="์™œ๋„ (Skewness)", value=f"{skewness:.4f}")
71
-
72
- if skewness > 0.5:
73
- st.info("๊ผฌ๋ฆฌ๊ฐ€ ์˜ค๋ฅธ์ชฝ์œผ๋กœ ๊ธด ๋ถ„ํฌ (Positive Skew): ๋Œ€๋ถ€๋ถ„์˜ ํ•™์ƒ๋“ค์ด ํ‰๊ท ๋ณด๋‹ค ๋‚ฎ์€ ์ ์ˆ˜์— ๋ชฐ๋ ค์žˆ๊ณ , ์ผ๋ถ€ ๊ณ ๋“์ ์ž๋“ค์ด ํ‰๊ท ์„ ๋†’์ด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.")
74
- elif skewness < -0.5:
75
- st.info("๊ผฌ๋ฆฌ๊ฐ€ ์™ผ์ชฝ์œผ๋กœ ๊ธด ๋ถ„ํฌ (Negative Skew): ๋Œ€๋ถ€๋ถ„์˜ ํ•™์ƒ๋“ค์ด ํ‰๊ท ๋ณด๋‹ค ๋†’์€ ์ ์ˆ˜์— ๋ชฐ๋ ค์žˆ๊ณ , ์ผ๋ถ€ ์ €๋“์ ์ž๋“ค์ด ํ‰๊ท ์„ ๋‚ฎ์ถ”๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.")
76
- else:
77
- st.info("๋Œ€์นญ์— ๊ฐ€๊นŒ์šด ๋ถ„ํฌ: ์ ์ˆ˜๊ฐ€ ํ‰๊ท ์„ ์ค‘์‹ฌ์œผ๋กœ ๋น„๊ต์  ๊ณ ๋ฅด๊ฒŒ ๋ถ„ํฌ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.")
 
 
 
78
 
79
- # --- ๋ฉ”์ธ ์‹คํ–‰ ํ•จ์ˆ˜ ---
80
  def main():
81
- st.set_page_config(layout="wide") # ํŽ˜์ด์ง€ ๋ ˆ์ด์•„์›ƒ์„ ๋„“๊ฒŒ ์„ค์ •
82
- set_korean_font() # ์•ฑ ์‹œ์ž‘ ์‹œ ํฐํŠธ ์„ค์ • ๋จผ์ € ์‹คํ–‰
 
 
 
 
 
 
83
 
84
  st.title("ํ•™์ƒ ์ ์ˆ˜ ๋ถ„ํฌ ๋ถ„์„ ๋„๊ตฌ ๐Ÿ“Š")
85
  st.write("CSV ํŒŒ์ผ์„ ์ง์ ‘ ์—…๋กœ๋“œํ•˜๊ฑฐ๋‚˜ Google Sheets URL์„ ๋ถ™์—ฌ๋„ฃ์–ด ํ•™์ƒ ์ ์ˆ˜ ๋ถ„ํฌ๋ฅผ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค.")
86
  st.write("---")
87
 
88
- st.sidebar.title("๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ")
89
- source_option = st.sidebar.radio("๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ์„ ํƒํ•˜์„ธ์š”:", ("Google Sheets URL", "CSV ํŒŒ์ผ ์—…๋กœ๋“œ"))
 
 
 
90
 
91
  df = None
92
 
93
- if source_option == "Google Sheets URL":
94
- sample_url = "https://docs.google.com/spreadsheets/d/e/2PACX-1vQ2Z8kzJq2sM7w2_9gXo-jZ-mO5o-BvC-w5p2nJ6oJ7oJ9xL-w3kZ9j5Z3kX7vN1aQ4mB1cW8jB7fR/pub?gid=0&single=true&output=csv"
95
- url = st.sidebar.text_input("์›น์— ๊ฒŒ์‹œ๋œ Google Sheets CSV URL", value=sample_url)
96
- if url:
97
- try:
98
- df = pd.read_csv(url)
99
- except Exception as e:
100
- st.error(f"URL๋กœ๋ถ€ํ„ฐ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {e}")
101
- st.warning("์˜ฌ๋ฐ”๋ฅธ Google Sheets '์›น ๊ฒŒ์‹œ' CSV URL์ธ์ง€ ํ™•์ธํ•ด์ฃผ์„ธ์š”.")
102
-
103
- # [์ˆ˜์ • 2] elif์˜ ๋“ค์—ฌ์“ฐ๊ธฐ๋ฅผ ์ˆ˜์ •ํ•˜์—ฌ if์™€ ๊ฐ™์€ ๋ ˆ๋ฒจ๋กœ ๋งž์ถค
104
- elif source_option == "CSV ํŒŒ์ผ ์—…๋กœ๋“œ":
105
- uploaded_file = st.sidebar.file_uploader("CSV ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜์„ธ์š”.", type="csv")
106
  if uploaded_file:
107
  try:
108
- df = pd.read_csv(uploaded_file, encoding='utf-8-sig')
 
 
 
 
 
 
 
 
 
 
 
 
109
  except Exception as e:
110
  st.error(f"ํŒŒ์ผ์„ ์ฝ๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {e}")
111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  # ๋ฐ์ดํ„ฐ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ๋กœ๋“œ๋œ ๊ฒฝ์šฐ์—๋งŒ ๋ถ„์„ ํ•จ์ˆ˜ ์‹คํ–‰
113
- if df is not None:
 
114
  analyze_scores(df)
115
  else:
116
- st.info("์‚ฌ์ด๋“œ๋ฐ”์—์„œ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ์„ ํƒํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์™€์ฃผ์„ธ์š”.")
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
  if __name__ == '__main__':
119
  main()
 
6
  from scipy.stats import norm, skew
7
  import platform
8
  import os
9
+ import matplotlib.font_manager as fm
10
 
11
  def set_korean_font():
12
+ """ํ•œ๊ธ€ ํฐํŠธ ์„ค์ • ํ•จ์ˆ˜ - ์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ•์œผ๋กœ ์‹œ๋„"""
13
+
14
+ # 1. ์‹œ์Šคํ…œ ๊ธฐ๋ณธ ํ•œ๊ธ€ ํฐํŠธ ์ฐพ๊ธฐ
15
+ available_fonts = [f.name for f in fm.fontManager.ttflist]
16
+ korean_fonts = [
17
+ 'NanumGothic', 'NanumBarunGothic', 'NanumSquare',
18
+ 'Malgun Gothic', 'AppleGothic', 'Noto Sans CJK KR',
19
+ 'DejaVu Sans', 'Arial Unicode MS'
20
+ ]
21
+
22
+ selected_font = None
23
+
24
+ # 2. ์‚ฌ์šฉ์ž ์ง€์ • ํฐํŠธ ํŒŒ์ผ ํ™•์ธ
25
  font_filename = "NanumGaRamYeonGgoc.ttf"
 
26
  font_path = os.path.join(os.getcwd(), font_filename)
27
+
28
+ if os.path.exists(font_path):
29
  try:
30
+ # ํฐํŠธ ํŒŒ์ผ์„ ์‹œ์Šคํ…œ์— ๋“ฑ๋ก
31
  fm.fontManager.addfont(font_path)
32
+ font_prop = fm.FontProperties(fname=font_path)
33
+ selected_font = font_prop.get_name()
34
+ plt.rcParams['font.family'] = selected_font
35
+ st.sidebar.success(f"์‚ฌ์šฉ์ž ํฐํŠธ '{selected_font}' ๋กœ๋”ฉ ์„ฑ๊ณต!")
36
  except Exception as e:
37
+ st.sidebar.warning(f"์‚ฌ์šฉ์ž ํฐํŠธ ๋กœ๋”ฉ ์‹คํŒจ: {e}")
38
+
39
+ # 3. ์‹œ์Šคํ…œ ํฐํŠธ์—์„œ ํ•œ๊ธ€ ํฐํŠธ ์ฐพ๊ธฐ
40
+ if not selected_font:
41
+ for font in korean_fonts:
42
+ if font in available_fonts:
43
+ selected_font = font
44
+ plt.rcParams['font.family'] = font
45
+ st.sidebar.info(f"์‹œ์Šคํ…œ ํฐํŠธ '{font}' ์‚ฌ์šฉ ์ค‘")
46
+ break
47
+
48
+ # 4. ๋ชจ๋“  ๋ฐฉ๋ฒ•์ด ์‹คํŒจํ•œ ๊ฒฝ์šฐ ๊ธฐ๋ณธ ์„ค์ •
49
+ if not selected_font:
50
+ # ์šด์˜์ฒด์ œ๋ณ„ ๊ธฐ๋ณธ ์„ค์ •
51
+ if platform.system() == 'Windows':
52
+ plt.rcParams['font.family'] = 'Malgun Gothic'
53
+ elif platform.system() == 'Darwin': # macOS
54
+ plt.rcParams['font.family'] = 'AppleGothic'
55
+ else: # Linux
56
+ plt.rcParams['font.family'] = 'DejaVu Sans'
57
+
58
  st.sidebar.warning(
59
+ f"ํ•œ๊ธ€ ํฐํŠธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์–ด ๊ธฐ๋ณธ ํฐํŠธ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.\n"
60
+ f"'{font_filename}' ํŒŒ์ผ์„ ์•ฑ๊ณผ ๊ฐ™์€ ํด๋”์— ๋„ฃ์œผ๋ฉด ๋” ์ข‹์€ ํ•œ๊ธ€ ํ‘œ์‹œ๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค."
61
  )
62
+
63
+ # ๋งˆ์ด๋„ˆ์Šค ๊ธฐํ˜ธ ๊นจ์ง ๋ฐฉ์ง€
64
  plt.rcParams['axes.unicode_minus'] = False
65
 
66
+ # ํ˜„์žฌ ์„ค์ •๋œ ํฐํŠธ ์ •๋ณด ํ‘œ์‹œ
67
+ st.sidebar.text(f"ํ˜„์žฌ ํฐํŠธ: {plt.rcParams['font.family']}")
68
+
69
  def analyze_scores(df):
70
  """๋ฐ์ดํ„ฐํ”„๋ ˆ์ž„์„ ๋ฐ›์•„ ๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ํ‘œ์‹œํ•˜๋Š” ํ•จ์ˆ˜"""
71
  st.subheader("๋ฐ์ดํ„ฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ (์ƒ์œ„ 5๊ฐœ)")
 
81
 
82
  if score_column:
83
  scores = df[score_column].dropna()
84
+
85
+ # ์œ ํšจํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ
86
+ if len(scores) == 0:
87
+ st.error("์„ ํƒํ•œ ์—ด์— ์œ ํšจํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
88
+ return
89
+
90
  st.subheader(f"'{score_column}' ์ ์ˆ˜ ๋ถ„ํฌ ๋ถ„์„ ๊ฒฐ๊ณผ")
91
 
92
  # 1. ๊ธฐ์ˆ  ํ†ต๊ณ„๋Ÿ‰
 
96
  # 2. ๋ถ„ํฌ ์‹œ๊ฐํ™”
97
  st.write("#### ๐ŸŽจ ์ ์ˆ˜ ๋ถ„ํฌ ์‹œ๊ฐํ™”")
98
  fig, ax = plt.subplots(figsize=(10, 6))
99
+
100
+ try:
101
+ sns.histplot(scores, kde=True, stat='density', label='ํ•™์ƒ ์ ์ˆ˜ ๋ถ„ํฌ', ax=ax)
102
+ mu, std = norm.fit(scores)
103
+ xmin, xmax = ax.get_xlim()
104
+ x = np.linspace(xmin, xmax, 100)
105
+ p = norm.pdf(x, mu, std)
106
+ ax.plot(x, p, 'k', linewidth=2, label='์ •๊ทœ๋ถ„ํฌ ๊ณก์„ ')
107
+ ax.set_title(f"'{score_column}' ์ ์ˆ˜ ๋ถ„ํฌ (ํ‰๊ท : {mu:.2f}, ํ‘œ์ค€ํŽธ์ฐจ: {std:.2f})")
108
+ ax.set_xlabel('์ ์ˆ˜')
109
+ ax.set_ylabel('๋ฐ€๋„')
110
+ ax.legend()
111
+ st.pyplot(fig)
112
+ except Exception as e:
113
+ st.error(f"๊ทธ๋ž˜ํ”„ ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜ ๏ฟฝ๏ฟฝ์ƒ: {e}")
114
+ finally:
115
+ plt.close(fig) # ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€
116
 
117
  # 3. ์™œ๋„(Skewness) ๋ถ„์„
118
  st.write("#### ๐Ÿ“ ์™œ๋„ (Skewness) ๋ถ„์„")
119
+ try:
120
+ skewness = skew(scores)
121
+ st.metric(label="์™œ๋„ (Skewness)", value=f"{skewness:.4f}")
122
+
123
+ if skewness > 0.5:
124
+ st.info("๐Ÿ”ด ๊ผฌ๋ฆฌ๊ฐ€ ์˜ค๋ฅธ์ชฝ์œผ๋กœ ๊ธด ๋ถ„ํฌ (Positive Skew): ๋Œ€๋ถ€๋ถ„์˜ ํ•™์ƒ๋“ค์ด ํ‰๊ท ๋ณด๋‹ค ๋‚ฎ์€ ์ ์ˆ˜์— ๋ชฐ๋ ค์žˆ๊ณ , ์ผ๋ถ€ ๊ณ ๋“์ ์ž๋“ค์ด ํ‰๊ท ์„ ๋†’์ด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.")
125
+ elif skewness < -0.5:
126
+ st.info("๐Ÿ”ต ๊ผฌ๋ฆฌ๊ฐ€ ์™ผ์ชฝ์œผ๋กœ ๊ธด ๋ถ„ํฌ (Negative Skew): ๋Œ€๋ถ€๋ถ„์˜ ํ•™์ƒ๋“ค์ด ํ‰๊ท ๋ณด๋‹ค ๋†’์€ ์ ์ˆ˜์— ๋ชฐ๋ ค์žˆ๊ณ , ์ผ๋ถ€ ์ €๋“์ ์ž๋“ค์ด ํ‰๊ท ์„ ๋‚ฎ์ถ”๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.")
127
+ else:
128
+ st.info("๐ŸŸข ๋Œ€์นญ์— ๊ฐ€๊นŒ์šด ๋ถ„ํฌ: ์ ์ˆ˜๊ฐ€ ํ‰๊ท ์„ ์ค‘์‹ฌ์œผ๋กœ ๋น„๊ต์  ๊ณ ๋ฅด๊ฒŒ ๋ถ„ํฌ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.")
129
+ except Exception as e:
130
+ st.error(f"์™œ๋„ ๊ณ„์‚ฐ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {e}")
131
 
 
132
  def main():
133
+ st.set_page_config(
134
+ page_title="ํ•™์ƒ ์ ์ˆ˜ ๋ถ„์„",
135
+ page_icon="๐Ÿ“Š",
136
+ layout="wide"
137
+ )
138
+
139
+ # ํฐํŠธ ์„ค์ •์„ ๋จผ์ € ์‹คํ–‰
140
+ set_korean_font()
141
 
142
  st.title("ํ•™์ƒ ์ ์ˆ˜ ๋ถ„ํฌ ๋ถ„์„ ๋„๊ตฌ ๐Ÿ“Š")
143
  st.write("CSV ํŒŒ์ผ์„ ์ง์ ‘ ์—…๋กœ๋“œํ•˜๊ฑฐ๋‚˜ Google Sheets URL์„ ๋ถ™์—ฌ๋„ฃ์–ด ํ•™์ƒ ์ ์ˆ˜ ๋ถ„ํฌ๋ฅผ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค.")
144
  st.write("---")
145
 
146
+ st.sidebar.title("๐Ÿ“ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ")
147
+ source_option = st.sidebar.radio(
148
+ "๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ์„ ํƒํ•˜์„ธ์š”:",
149
+ ("CSV ํŒŒ์ผ ์—…๋กœ๋“œ", "Google Sheets URL")
150
+ )
151
 
152
  df = None
153
 
154
+ if source_option == "CSV ํŒŒ์ผ ์—…๋กœ๋“œ":
155
+ uploaded_file = st.sidebar.file_uploader(
156
+ "CSV ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜์„ธ์š”.",
157
+ type=["csv"],
158
+ help="UTF-8 ๋˜๋Š” CP949 ์ธ์ฝ”๋”ฉ๋œ CSV ํŒŒ์ผ์„ ์„ ํƒํ•˜์„ธ์š”."
159
+ )
 
 
 
 
 
 
 
160
  if uploaded_file:
161
  try:
162
+ # ์—ฌ๋Ÿฌ ์ธ์ฝ”๋”ฉ ๋ฐฉ์‹์œผ๋กœ ์‹œ๋„
163
+ encodings = ['utf-8-sig', 'utf-8', 'cp949', 'euc-kr']
164
+ for encoding in encodings:
165
+ try:
166
+ df = pd.read_csv(uploaded_file, encoding=encoding)
167
+ st.sidebar.success(f"ํŒŒ์ผ ์ฝ๊ธฐ ์„ฑ๊ณต! (์ธ์ฝ”๋”ฉ: {encoding})")
168
+ break
169
+ except UnicodeDecodeError:
170
+ continue
171
+
172
+ if df is None:
173
+ st.error("ํŒŒ์ผ ์ธ์ฝ”๋”ฉ์„ ์ธ์‹ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. UTF-8 ๋˜๋Š” CP949๋กœ ์ €์žฅ๋œ ํŒŒ์ผ์„ ์‚ฌ์šฉํ•ด์ฃผ์„ธ์š”.")
174
+
175
  except Exception as e:
176
  st.error(f"ํŒŒ์ผ์„ ์ฝ๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {e}")
177
 
178
+ elif source_option == "Google Sheets URL":
179
+ st.sidebar.info("Google Sheets๋ฅผ '์›น์— ๊ฒŒ์‹œ'ํ•˜๊ณ  CSV ํ˜•์‹์˜ URL์„ ์ž…๋ ฅํ•˜์„ธ์š”.")
180
+ sample_url = "https://docs.google.com/spreadsheets/d/e/2PACX-1vSGdxUwlxDVZXlqFJtLMPcZbKt_B3qBAUSj-hO-EwnG25iQRR3wxWVT9X54a1XkhBVw-BSEIXfeWKg6/pub?gid=966112810&single=true&output=csv"
181
+ url = st.sidebar.text_input(
182
+ "Google Sheets CSV URL",
183
+ value="",
184
+ placeholder="https://docs.google.com/spreadsheets/d/..."
185
+ )
186
+
187
+ if url:
188
+ if "docs.google.com" not in url:
189
+ st.sidebar.warning("์˜ฌ๋ฐ”๋ฅธ Google Sheets URL์ธ์ง€ ํ™•์ธํ•ด์ฃผ์„ธ์š”.")
190
+ else:
191
+ try:
192
+ with st.spinner("๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘..."):
193
+ df = pd.read_csv(url)
194
+ st.sidebar.success("Google Sheets ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์„ฑ๊ณต!")
195
+ except Exception as e:
196
+ st.error(f"URL๋กœ๋ถ€ํ„ฐ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {e}")
197
+ st.warning("์˜ฌ๋ฐ”๋ฅธ Google Sheets '์›น ๊ฒŒ์‹œ' CSV URL์ธ์ง€ ํ™•์ธํ•ด์ฃผ์„ธ์š”.")
198
+
199
  # ๋ฐ์ดํ„ฐ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ๋กœ๋“œ๋œ ๊ฒฝ์šฐ์—๋งŒ ๋ถ„์„ ํ•จ์ˆ˜ ์‹คํ–‰
200
+ if df is not None and not df.empty:
201
+ st.success(f"๋ฐ์ดํ„ฐ ๋กœ๋”ฉ ์™„๋ฃŒ! (ํ–‰: {len(df)}, ์—ด: {len(df.columns)})")
202
  analyze_scores(df)
203
  else:
204
+ st.info("๐Ÿ‘ˆ ์‚ฌ์ด๋“œ๋ฐ”์—์„œ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋ฅผ ์„ ํƒํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์™€์ฃผ์„ธ์š”.")
205
+
206
+ # ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ ๋ฒ„ํŠผ
207
+ if st.button("๐ŸŽฒ ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ๋กœ ํ…Œ์ŠคํŠธ"):
208
+ np.random.seed(42)
209
+ sample_data = {
210
+ 'ํ•™์ƒ๋ฒˆํ˜ธ': range(1, 101),
211
+ '์ˆ˜ํ•™์ ์ˆ˜': np.random.normal(75, 15, 100).clip(0, 100).round(1),
212
+ '์˜์–ด์ ์ˆ˜': np.random.normal(80, 12, 100).clip(0, 100).round(1),
213
+ '๊ณผํ•™์ ์ˆ˜': np.random.normal(70, 18, 100).clip(0, 100).round(1)
214
+ }
215
+ df = pd.DataFrame(sample_data)
216
+ st.success("์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!")
217
+ analyze_scores(df)
218
 
219
  if __name__ == '__main__':
220
  main()