soojeongcrystal commited on
Commit
66a6842
·
verified ·
1 Parent(s): 33d9085

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +63 -45
app.py CHANGED
@@ -9,7 +9,6 @@ import re
9
  import os
10
  import altair as alt
11
  import colorsys
12
- import streamlit.components.v1 as components
13
  import matplotlib.pyplot as plt
14
  import networkx as nx
15
  import io
@@ -66,7 +65,6 @@ def plot_network_graph(G):
66
  nx.draw(G, pos, node_color=node_colors, with_labels=True, node_size=3000, font_size=8,
67
  font_weight='bold', edge_color='gray', width=0.5)
68
 
69
- # 노드 라벨을 다시 그려 배경을 하얀색으로 만듭니다
70
  for node, (x, y) in pos.items():
71
  plt.text(x, y, node, fontsize=8, fontweight='bold', ha='center', va='center',
72
  bbox=dict(facecolor='white', edgecolor='none', alpha=0.7, pad=0.5))
@@ -74,7 +72,6 @@ def plot_network_graph(G):
74
  plt.title("Topic Word Network", fontsize=16)
75
  plt.axis('off')
76
 
77
- # 이미지를 바이트 스트림으로 저장
78
  img_bytes = io.BytesIO()
79
  plt.savefig(img_bytes, format='png', dpi=300, bbox_inches='tight')
80
  img_bytes.seek(0)
@@ -128,64 +125,50 @@ with st.sidebar:
128
  # 파일 업로드
129
  uploaded_file = st.file_uploader("CSV 파일을 업로드하세요", type="csv")
130
 
131
- if uploaded_file is not None:
132
- try:
133
- df = pd.read_csv(uploaded_file)
 
 
 
 
 
 
 
 
 
134
  text_column = st.selectbox("텍스트 컬럼을 선택하세요", df.columns)
135
  num_topics = st.slider("토픽 수를 선택하세요", 2, 20, 5)
 
136
  if st.button("토픽 모델링 실행"):
137
  st.session_state.run_analysis = True
 
 
138
  else:
139
  st.session_state.run_analysis = False
140
- except Exception as e:
141
- st.error(f"파일을 읽는 중 오류가 발생했습니다: {str(e)}")
142
-
143
- # 파일 미리보기 (본문에서)
144
- # 파일 미리보기 (본문에서)
145
- if uploaded_file is not None:
146
- try:
147
- # 파일 내용 확인
148
- file_contents = uploaded_file.getvalue().decode('utf-8')
149
- if not file_contents.strip():
150
- st.error("업로드된 CSV 파일이 비어 있습니다.")
151
- else:
152
- # CSV 파일 읽기 시도
153
- df = pd.read_csv(uploaded_file, encoding='utf-8')
154
- if df.empty:
155
- st.error("CSV 파일에 데이터가 없습니다.")
156
- else:
157
- st.success("파일이 성공적으로 업로드되었습니다.")
158
- st.subheader("데이터 미리보기")
159
- st.write(df.head())
160
-
161
- # 텍스트 컬럼 선택
162
- text_column = st.selectbox("텍스트 컬럼을 선택하세요", df.columns)
163
- num_topics = st.slider("토픽 수를 선택하세요", 2, 20, 5)
164
- if st.button("토픽 모델링 실행"):
165
- st.session_state.run_analysis = True
166
- else:
167
- st.session_state.run_analysis = False
168
  except pd.errors.EmptyDataError:
169
- st.error("업로드된 CSV 파일이 비어있거나 올바르지 않습니다. 다시 확인해주세요.")
170
  except UnicodeDecodeError:
171
  st.error("파일 인코딩에 문제가 있습니다. UTF-8 인코딩으로 저장된 CSV 파일을 사용해주세요.")
172
  except Exception as e:
173
  st.error(f"파일을 읽는 중 오류가 발생했습니다: {str(e)}")
 
 
174
 
175
- # 메인 컨텐츠
176
  if 'run_analysis' in st.session_state and st.session_state.run_analysis:
177
- if uploaded_file is not None and 'df' in locals() and not df.empty:
178
  try:
179
  # 텍스트 전처리
180
  with st.spinner("텍스트 전처리 중..."):
181
- df['processed_text'] = df[text_column].apply(lambda x: preprocess_text(x, stop_words))
182
 
183
  # 토픽 모델링
184
  with st.spinner("토픽 모델링 실행 중..."):
185
  vectorizer = CountVectorizer(max_df=0.95, min_df=2)
186
  doc_term_matrix = vectorizer.fit_transform(df['processed_text'])
187
 
188
- lda = LatentDirichletAllocation(n_components=num_topics, random_state=42)
189
  lda_output = lda.fit_transform(doc_term_matrix)
190
 
191
  # TF-IDF 계산
@@ -198,7 +181,6 @@ if 'run_analysis' in st.session_state and st.session_state.run_analysis:
198
 
199
  topic_results = []
200
 
201
- # 토픽 요약을 callout 스타일로 표시
202
  for idx, topic in enumerate(lda.components_):
203
  lda_top_words = [(feature_names[i], topic[i]) for i in topic.argsort()[:-11:-1]]
204
  topic_docs = lda_output[:, idx].argsort()[::-1][:100]
@@ -206,7 +188,6 @@ if 'run_analysis' in st.session_state and st.session_state.run_analysis:
206
  tfidf_top_words = [(feature_names[i], topic_tfidf[i]) for i in topic_tfidf.argsort()[:-11:-1]]
207
  weight = lda_output[:, idx].mean() * 100
208
 
209
- # 상위 3개 단어를 사용하여 토픽 이름 생성
210
  topic_name = ", ".join([word for word, _ in lda_top_words[:3]])
211
 
212
  topic_results.append({
@@ -223,19 +204,16 @@ if 'run_analysis' in st.session_state and st.session_state.run_analysis:
223
  for idx, topic in enumerate(lda.components_):
224
  st.subheader(f"토픽 {idx + 1}")
225
 
226
- # LDA 상위 단어와 TF-IDF 상위 단어를 나란히 표시
227
  col1, col2 = st.columns(2)
228
 
229
  with col1:
230
- # LDA 상위 단어 테이블
231
  lda_top_words = [(feature_names[i], topic[i]) for i in topic.argsort()[:-11:-1]]
232
  df_lda = pd.DataFrame(lda_top_words, columns=['단어', 'LDA 점수'])
233
  st.subheader("LDA 상위 단어")
234
  st.table(df_lda.style.format({'LDA 점수': '{:.4f}'}))
235
 
236
  with col2:
237
- # 토픽별 TF-IDF 계산
238
- topic_docs = lda_output[:, idx].argsort()[::-1][:100] # 상위 100개 문서 선택
239
  topic_tfidf = tfidf_matrix[topic_docs].mean(axis=0).A1
240
  tfidf_top_words = [(feature_names[i], topic_tfidf[i]) for i in topic_tfidf.argsort()[:-11:-1]]
241
  df_tfidf = pd.DataFrame(tfidf_top_words, columns=['단어', 'TF-IDF'])
@@ -303,6 +281,45 @@ if 'run_analysis' in st.session_state and st.session_state.run_analysis:
303
 
304
 
305
  # Claude API를 사용하여 토픽 해석
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
  if api_key:
307
  client = Anthropic(api_key=api_key)
308
 
@@ -340,6 +357,7 @@ if 'run_analysis' in st.session_state and st.session_state.run_analysis:
340
  3. 토픽명 설명에서는 왜 그러한 토픽명이 선택되었는지, LDA와 TF-IDF 상위 단어들과의 연관성을 설명해주세요.
341
 
342
  위 형식에 맞춰 답변해주세요. 사용자가 쉽게 복사하여 사용할 수 있도록 간결하고 명확하게 작성해주세요."""
 
343
  try:
344
  response = client.completions.create(
345
  model="claude-2.1",
 
9
  import os
10
  import altair as alt
11
  import colorsys
 
12
  import matplotlib.pyplot as plt
13
  import networkx as nx
14
  import io
 
65
  nx.draw(G, pos, node_color=node_colors, with_labels=True, node_size=3000, font_size=8,
66
  font_weight='bold', edge_color='gray', width=0.5)
67
 
 
68
  for node, (x, y) in pos.items():
69
  plt.text(x, y, node, fontsize=8, fontweight='bold', ha='center', va='center',
70
  bbox=dict(facecolor='white', edgecolor='none', alpha=0.7, pad=0.5))
 
72
  plt.title("Topic Word Network", fontsize=16)
73
  plt.axis('off')
74
 
 
75
  img_bytes = io.BytesIO()
76
  plt.savefig(img_bytes, format='png', dpi=300, bbox_inches='tight')
77
  img_bytes.seek(0)
 
125
  # 파일 업로드
126
  uploaded_file = st.file_uploader("CSV 파일을 업로드하세요", type="csv")
127
 
128
+ # 파일 미리보기 분석 설정
129
+ if uploaded_file is not None:
130
+ try:
131
+ df = pd.read_csv(uploaded_file)
132
+ if df.empty:
133
+ st.error("CSV 파일에 데이터가 없습니다.")
134
+ else:
135
+ st.success("파일이 성공적으로 업로드되었습니다.")
136
+ st.subheader("데이터 미리보기")
137
+ st.write(df.head())
138
+
139
+ # 텍스트 컬럼 선택
140
  text_column = st.selectbox("텍스트 컬럼을 선택하세요", df.columns)
141
  num_topics = st.slider("토픽 수를 선택하세요", 2, 20, 5)
142
+
143
  if st.button("토픽 모델링 실행"):
144
  st.session_state.run_analysis = True
145
+ st.session_state.text_column = text_column
146
+ st.session_state.num_topics = num_topics
147
  else:
148
  st.session_state.run_analysis = False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  except pd.errors.EmptyDataError:
150
+ st.error("업로드된 CSV 파일이 비어있습니다. 다시 확인해주세요.")
151
  except UnicodeDecodeError:
152
  st.error("파일 인코딩에 문제가 있습니다. UTF-8 인코딩으로 저장된 CSV 파일을 사용해주세요.")
153
  except Exception as e:
154
  st.error(f"파일을 읽는 중 오류가 발생했습니다: {str(e)}")
155
+ else:
156
+ st.info("CSV 파일을 업로드해주세요.")
157
 
158
+ # 메인 컨텐츠 (토픽 모델링 실행)
159
  if 'run_analysis' in st.session_state and st.session_state.run_analysis:
160
+ if 'text_column' in st.session_state and 'num_topics' in st.session_state:
161
  try:
162
  # 텍스트 전처리
163
  with st.spinner("텍스트 전처리 중..."):
164
+ df['processed_text'] = df[st.session_state.text_column].apply(lambda x: preprocess_text(x, stop_words))
165
 
166
  # 토픽 모델링
167
  with st.spinner("토픽 모델링 실행 중..."):
168
  vectorizer = CountVectorizer(max_df=0.95, min_df=2)
169
  doc_term_matrix = vectorizer.fit_transform(df['processed_text'])
170
 
171
+ lda = LatentDirichletAllocation(n_components=st.session_state.num_topics, random_state=42)
172
  lda_output = lda.fit_transform(doc_term_matrix)
173
 
174
  # TF-IDF 계산
 
181
 
182
  topic_results = []
183
 
 
184
  for idx, topic in enumerate(lda.components_):
185
  lda_top_words = [(feature_names[i], topic[i]) for i in topic.argsort()[:-11:-1]]
186
  topic_docs = lda_output[:, idx].argsort()[::-1][:100]
 
188
  tfidf_top_words = [(feature_names[i], topic_tfidf[i]) for i in topic_tfidf.argsort()[:-11:-1]]
189
  weight = lda_output[:, idx].mean() * 100
190
 
 
191
  topic_name = ", ".join([word for word, _ in lda_top_words[:3]])
192
 
193
  topic_results.append({
 
204
  for idx, topic in enumerate(lda.components_):
205
  st.subheader(f"토픽 {idx + 1}")
206
 
 
207
  col1, col2 = st.columns(2)
208
 
209
  with col1:
 
210
  lda_top_words = [(feature_names[i], topic[i]) for i in topic.argsort()[:-11:-1]]
211
  df_lda = pd.DataFrame(lda_top_words, columns=['단어', 'LDA 점수'])
212
  st.subheader("LDA 상위 단어")
213
  st.table(df_lda.style.format({'LDA 점수': '{:.4f}'}))
214
 
215
  with col2:
216
+ topic_docs = lda_output[:, idx].argsort()[::-1][:100]
 
217
  topic_tfidf = tfidf_matrix[topic_docs].mean(axis=0).A1
218
  tfidf_top_words = [(feature_names[i], topic_tfidf[i]) for i in topic_tfidf.argsort()[:-11:-1]]
219
  df_tfidf = pd.DataFrame(tfidf_top_words, columns=['단어', 'TF-IDF'])
 
281
 
282
 
283
  # Claude API를 사용하여 토픽 해석
284
+ if api_key:
285
+ client = Anthropic(api_key=api_key)
286
+
287
+ st.header("Claude의 토픽 해석")
288
+ with st.spinner("토픽 해석 중..."):
289
+ prompt = f"""Human: 다음은 LDA 토픽 모델링 결과로 나온 각 토픽의 정보입니다. 이를 바탕으로 전체 토픽을 종합적으로 해석해주세요:
290
+
291
+ {", ".join([f"토픽 {{info['topic_num']}} (비중: {{info['weight']:.1f}}%)" for info in topic_results])}
292
+
293
+ 각 토픽의 주요 단어:
294
+ """
295
+ for info in topic_results:
296
+ prompt += f"""
297
+ 토픽 {info['topic_num']} (비중: {info['weight']:.1f}%):
298
+ LDA 상위 단어: {', '.join(info['lda_words'])}
299
+ TF-IDF 상위 단어: {', '.join(info['tfidf_words'])}
300
+ """
301
+
302
+ prompt += """
303
+ 위 정보를 바탕으로 다음 형식에 맞춰 답변해주세요:
304
+
305
+ 1. 전체 문서의 주제 요약 (3-4문장):
306
+ [여기에 전체 문서의 주제를 종합적으로 설명해주세요. 각 토픽의 비중을 고려하여 중요도를 반영해주세요.]
307
+
308
+ 2. 각 토픽 요약:
309
+ [각 토픽에 대해 다음 형식으로 요약해주세요]
310
+ - 토픽 [번호] ([LDA 기반 토픽명]): [비중]%
311
+ • 토픽명 설명: [토픽명이 이렇게 지어진 이유를 1-2문장으로 설명해주세요. LDA와 TF-IDF 상위 단어들이 어떻게 이 토픽명과 연관되는지 설명하세요.]
312
+ • 토픽 설명: [1-2문장으로 토픽의 전반적인 내용을 설명해주세요.]
313
+ • 가상의 예시 응답: "[이 토픽과 관련된 가상의 구성원 발언 예시를 넣어주세요. -한다 체를 지켜주세요]"
314
+
315
+ 주의사항:
316
+ 1. 토픽명은 "LDA 기반 [구체적인 토픽명]" 형식으로 작성해주세요. 예를 들어, "구성원들의 성장과 개인적인 역량개발 노력" 또는 "리더들의 노력과 조직의 전폭적인 지원" 등입니다.
317
+ 2. 토픽명은 단순히 단어를 나열하는 것이 아니라, 토픽의 핵심 주제나 의미를 잘 나타내는 구체적인 문구로 만들어주세요.
318
+ 3. 토픽명 설명에서는 왜 그러한 토픽명이 선택되었는지, LDA와 TF-IDF 상위 단어들과의 연관성을 설명해주세요.
319
+
320
+ 위 형식에 ��춰 답변해주세요. 사용자가 쉽게 복사하여 사용할 수 있도록 간결하고 명확하게 작성해주세요. """
321
+
322
+
323
  if api_key:
324
  client = Anthropic(api_key=api_key)
325
 
 
357
  3. 토픽명 설명에서는 왜 그러한 토픽명이 선택되었는지, LDA와 TF-IDF 상위 단어들과의 연관성을 설명해주세요.
358
 
359
  위 형식에 맞춰 답변해주세요. 사용자가 쉽게 복사하여 사용할 수 있도록 간결하고 명확하게 작성해주세요."""
360
+
361
  try:
362
  response = client.completions.create(
363
  model="claude-2.1",