soojeongcrystal commited on
Commit
0f3d973
·
verified ·
1 Parent(s): 2d8ab82

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +92 -83
app.py CHANGED
@@ -1,7 +1,7 @@
1
  import streamlit as st
2
  import pandas as pd
3
  import numpy as np
4
- from anthropic import Anthropic, HUMAN_PROMPT, AI_PROMPT
5
  from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
6
  from sklearn.decomposition import LatentDirichletAllocation
7
  from konlpy.tag import Okt
@@ -11,8 +11,6 @@ import altair as alt
11
  import colorsys
12
  import networkx as nx
13
  import streamlit.components.v1 as components
14
- import anthropic
15
-
16
 
17
  # Streamlit 페이지 설정
18
  st.set_page_config(layout="wide", page_title="📊 토픽모델링 for SK", page_icon="📊")
@@ -60,24 +58,44 @@ def create_network_graph(topic_results, num_words=30):
60
 
61
  # HTML 네트워크 그래프 생성 함수
62
  def create_custom_network_html(G):
63
- nodes = [{"id": n, "label": n} for n in G.nodes()]
64
  edges = [{"from": u, "to": v} for u, v in G.edges()]
65
  html_content = f"""
66
  <html>
67
  <head>
68
- <script type="text/javascript" src="https://unpkg.com/vis-network@standalone/umd/vis-network.min.js"></script>
 
 
 
 
 
 
 
69
  </head>
70
  <body>
71
- <div id="mynetwork" style="width: 600px; height: 400px; border: 1px solid lightgray;"></div>
72
  <script type="text/javascript">
73
- var nodes = {nodes};
74
- var edges = {edges};
75
  var container = document.getElementById('mynetwork');
76
  var data = {{
77
- nodes: new vis.DataSet(nodes),
78
- edges: new vis.DataSet(edges)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  }};
80
- var options = {{}};
81
  var network = new vis.Network(container, data, options);
82
  </script>
83
  </body>
@@ -85,27 +103,6 @@ def create_custom_network_html(G):
85
  """
86
  return html_content
87
 
88
-
89
- def visualize_network(G):
90
- if G is None or G.number_of_nodes() == 0:
91
- st.error("Graph is empty or not properly initialized.")
92
- return
93
-
94
- try:
95
- nt = Network(height="500px", width="100%", bgcolor="#222222", font_color="white")
96
- if G.number_of_nodes() > 0: # G에 노드가 존재하는지 확인
97
- nt.from_nx(G)
98
- else:
99
- st.error("No nodes in the graph to render.")
100
- return
101
-
102
- nt.show("network.html")
103
- with open("network.html", 'r', encoding='utf-8') as f:
104
- html_string = f.read()
105
- components.html(html_string, height=500)
106
- except Exception as e:
107
- st.error(f"Failed to render the network graph: {str(e)}")
108
-
109
  # 헤더 스타일 변경
110
  st.markdown("""
111
  <style>
@@ -116,6 +113,12 @@ st.markdown("""
116
  background-color: #f1f1f1;
117
  padding: 10px;
118
  }
 
 
 
 
 
 
119
  </style>
120
  <div style="background-color: #f1f1f1; padding: 10px; color: #707070; text-align: right; width: 100%;">
121
  mySUNI 행복 College 행복담당조직 Meet-Up
@@ -143,31 +146,25 @@ with st.sidebar:
143
  # 파일 업로드
144
  uploaded_file = st.file_uploader("CSV 파일을 업로드하세요", type="csv")
145
 
146
- if uploaded_file is not None:
147
- try:
148
- # 파일 내용 확인
149
- file_contents = uploaded_file.getvalue().decode('utf-8')
150
- if not file_contents.strip():
151
- st.error("업로드된 CSV 파일이 비어 있습니다.")
 
 
 
 
 
 
 
 
 
152
  else:
153
- df = pd.read_csv(uploaded_file)
154
- if df.empty:
155
- st.error("CSV 파일에 데이터가 없습니다.")
156
- else:
157
- st.success("파일이 성공적으로 업로드되었습니다.")
158
- st.write("데이터 미리보기:")
159
- st.write(df.head())
160
-
161
- text_column = st.selectbox("텍스트 컬럼을 선택하세요", df.columns)
162
- num_topics = st.slider("토픽 수를 선택하세요", 2, 20, 5)
163
- if st.button("토픽 모델링 실행"):
164
- st.session_state.run_analysis = True
165
- else:
166
- st.session_state.run_analysis = False
167
- except pd.errors.EmptyDataError:
168
- st.error("업로드된 CSV 파일이 비어있거나 올바르지 않습니다. 다시 확인해주세요.")
169
- except Exception as e:
170
- st.error(f"파일을 읽는 중 오류가 발생했습니다: {str(e)}")
171
 
172
  # 메인 컨텐츠
173
  if 'run_analysis' in st.session_state and st.session_state.run_analysis:
@@ -195,6 +192,23 @@ if 'run_analysis' in st.session_state and st.session_state.run_analysis:
195
 
196
  topic_results = []
197
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  for idx, topic in enumerate(lda.components_):
199
  st.subheader(f"토픽 {idx + 1}")
200
 
@@ -206,7 +220,7 @@ if 'run_analysis' in st.session_state and st.session_state.run_analysis:
206
  lda_top_words = [(feature_names[i], topic[i]) for i in topic.argsort()[:-11:-1]]
207
  df_lda = pd.DataFrame(lda_top_words, columns=['단어', 'LDA 점수'])
208
  st.subheader("LDA 상위 단어")
209
- st.dataframe(df_lda.style.format({'LDA 점수': '{:.4f}'}), height=400)
210
 
211
  with col2:
212
  # 토픽별 TF-IDF 계산
@@ -215,14 +229,7 @@ if 'run_analysis' in st.session_state and st.session_state.run_analysis:
215
  tfidf_top_words = [(feature_names[i], topic_tfidf[i]) for i in topic_tfidf.argsort()[:-11:-1]]
216
  df_tfidf = pd.DataFrame(tfidf_top_words, columns=['단어', 'TF-IDF'])
217
  st.subheader("TF-IDF 상위 단어")
218
- st.dataframe(df_tfidf.style.format({'TF-IDF': '{:.4f}'}), height=400)
219
-
220
- topic_results.append({
221
- 'topic_num': idx + 1,
222
- 'lda_words': [word for word, _ in lda_top_words],
223
- 'tfidf_words': [word for word, _ in tfidf_top_words],
224
- 'weight': lda_output[:, idx].mean() * 100 # 퍼센트로 변환
225
- })
226
 
227
  # 토픽 비중 그래프
228
  st.header("토픽 비중 그래프")
@@ -267,20 +274,22 @@ if 'run_analysis' in st.session_state and st.session_state.run_analysis:
267
 
268
  st.altair_chart(chart, use_container_width=True)
269
 
270
- # 새로운 시각화 함수 호출
271
  st.header("토픽 단어 네트워크 그래프")
272
- G = create_network_graph(topic_results)
273
- html_content = create_custom_network_html(G)
274
- components.html(html_content, height=500)
 
 
 
275
 
276
-
277
  # Claude API를 사용하여 토픽 해석
278
  if api_key:
279
- client = anthropic.Anthropic(api_key=api_key)
280
-
281
  st.header("Claude의 토픽 해석")
282
  with st.spinner("토픽 해석 중..."):
283
- prompt = f"""{HUMAN_PROMPT} 다음은 LDA 토픽 모델링 결과로 나온 각 토픽의 정보입니다. 이를 바탕으로 전체 토픽을 종합적으로 해석해주세요:
284
 
285
  {", ".join([f"토픽 {info['topic_num']} (비중: {info['weight']:.1f}%)" for info in topic_results])}
286
 
@@ -313,16 +322,16 @@ if 'run_analysis' in st.session_state and st.session_state.run_analysis:
313
 
314
  위 형식에 맞춰 답변해주세요. 사용자가 쉽게 복사하여 사용할 수 있도록 간결하고 명확하게 작성해주세요."""
315
 
316
- response = client.messages.create(
317
- model="claude-3-5-sonnet-20240620",
318
- max_tokens=3000,
319
- messages=[
320
- {"role": "user", "content": f"{prompt}\n\n{AI_PROMPT}"}
321
- ]
322
- )
323
-
324
- st.subheader("토픽 모델링 종합 결과")
325
- st.text_area("결과를 복사하여 사용하세요:", value=response.completion, height=500)
326
  else:
327
  st.warning("Claude API 키가 설정되지 않았습니다. https://console.anthropic.com/settings/keys 에 접속하여 API 키를 발급받으시면 토픽명과 해석을 제공받으실 수 있습니다.")
328
 
 
1
  import streamlit as st
2
  import pandas as pd
3
  import numpy as np
4
+ from anthropic import Anthropic
5
  from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
6
  from sklearn.decomposition import LatentDirichletAllocation
7
  from konlpy.tag import Okt
 
11
  import colorsys
12
  import networkx as nx
13
  import streamlit.components.v1 as components
 
 
14
 
15
  # Streamlit 페이지 설정
16
  st.set_page_config(layout="wide", page_title="📊 토픽모델링 for SK", page_icon="📊")
 
58
 
59
  # HTML 네트워크 그래프 생성 함수
60
  def create_custom_network_html(G):
61
+ nodes = [{"id": n, "label": n, "color": G.nodes[n].get('color', '#000000')} for n in G.nodes()]
62
  edges = [{"from": u, "to": v} for u, v in G.edges()]
63
  html_content = f"""
64
  <html>
65
  <head>
66
+ <script type="text/javascript" src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
67
+ <style type="text/css">
68
+ #mynetwork {{
69
+ width: 100%;
70
+ height: 500px;
71
+ border: 1px solid lightgray;
72
+ }}
73
+ </style>
74
  </head>
75
  <body>
76
+ <div id="mynetwork"></div>
77
  <script type="text/javascript">
78
+ var nodes = new vis.DataSet({nodes});
79
+ var edges = new vis.DataSet({edges});
80
  var container = document.getElementById('mynetwork');
81
  var data = {{
82
+ nodes: nodes,
83
+ edges: edges
84
+ }};
85
+ var options = {{
86
+ nodes: {{
87
+ shape: 'dot',
88
+ size: 20,
89
+ font: {{
90
+ size: 15,
91
+ color: '#000000'
92
+ }},
93
+ borderWidth: 2
94
+ }},
95
+ edges: {{
96
+ width: 1
97
+ }}
98
  }};
 
99
  var network = new vis.Network(container, data, options);
100
  </script>
101
  </body>
 
103
  """
104
  return html_content
105
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  # 헤더 스타일 변경
107
  st.markdown("""
108
  <style>
 
113
  background-color: #f1f1f1;
114
  padding: 10px;
115
  }
116
+ .topic-summary {
117
+ background-color: #f0f2f6;
118
+ border-left: 5px solid #4e8098;
119
+ padding: 10px;
120
+ margin-bottom: 10px;
121
+ }
122
  </style>
123
  <div style="background-color: #f1f1f1; padding: 10px; color: #707070; text-align: right; width: 100%;">
124
  mySUNI 행복 College 행복담당조직 Meet-Up
 
146
  # 파일 업로드
147
  uploaded_file = st.file_uploader("CSV 파일을 업로드하세요", type="csv")
148
 
149
+ # 파일 미리보기 분석 실행 (본문에서)
150
+ if uploaded_file is not None:
151
+ try:
152
+ df = pd.read_csv(uploaded_file)
153
+ if df.empty:
154
+ st.error("CSV 파일에 데이터가 없습니다.")
155
+ else:
156
+ st.success("파일이 성공적으로 업로드되었습니다.")
157
+ st.subheader("데이터 미리보기")
158
+ st.write(df.head())
159
+
160
+ text_column = st.selectbox("텍스트 컬럼을 선택하세요", df.columns)
161
+ num_topics = st.slider("토픽 수를 선택하세요", 2, 20, 5)
162
+ if st.button("토픽 모델링 실행"):
163
+ st.session_state.run_analysis = True
164
  else:
165
+ st.session_state.run_analysis = False
166
+ except Exception as e:
167
+ st.error(f"파일을 읽는 오류가 발생했습니다: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
 
169
  # 메인 컨텐츠
170
  if 'run_analysis' in st.session_state and st.session_state.run_analysis:
 
192
 
193
  topic_results = []
194
 
195
+ # 토픽 요약을 callout 스타일로 표시
196
+ for idx, topic in enumerate(lda.components_):
197
+ lda_top_words = [(feature_names[i], topic[i]) for i in topic.argsort()[:-11:-1]]
198
+ topic_docs = lda_output[:, idx].argsort()[::-1][:100]
199
+ topic_tfidf = tfidf_matrix[topic_docs].mean(axis=0).A1
200
+ tfidf_top_words = [(feature_names[i], topic_tfidf[i]) for i in topic_tfidf.argsort()[:-11:-1]]
201
+ weight = lda_output[:, idx].mean() * 100
202
+ topic_results.append({
203
+ 'topic_num': idx + 1,
204
+ 'lda_words': [word for word, _ in lda_top_words],
205
+ 'tfidf_words': [word for word, _ in tfidf_top_words],
206
+ 'weight': weight
207
+ })
208
+
209
+ topic_summary = ", ".join([f"토픽 {info['topic_num']} (비중: {info['weight']:.1f}%)" for info in topic_results])
210
+ st.markdown(f'<div class="topic-summary">{topic_summary}</div>', unsafe_allow_html=True)
211
+
212
  for idx, topic in enumerate(lda.components_):
213
  st.subheader(f"토픽 {idx + 1}")
214
 
 
220
  lda_top_words = [(feature_names[i], topic[i]) for i in topic.argsort()[:-11:-1]]
221
  df_lda = pd.DataFrame(lda_top_words, columns=['단어', 'LDA 점수'])
222
  st.subheader("LDA 상위 단어")
223
+ st.table(df_lda.style.format({'LDA 점수': '{:.4f}'}))
224
 
225
  with col2:
226
  # 토픽별 TF-IDF 계산
 
229
  tfidf_top_words = [(feature_names[i], topic_tfidf[i]) for i in topic_tfidf.argsort()[:-11:-1]]
230
  df_tfidf = pd.DataFrame(tfidf_top_words, columns=['단어', 'TF-IDF'])
231
  st.subheader("TF-IDF 상위 단어")
232
+ st.table(df_tfidf.style.format({'TF-IDF': '{:.4f}'}))
 
 
 
 
 
 
 
233
 
234
  # 토픽 비중 그래프
235
  st.header("토픽 비중 그래프")
 
274
 
275
  st.altair_chart(chart, use_container_width=True)
276
 
277
+ # 네트워크 그래프 생성 및 시각화
278
  st.header("토픽 단어 네트워크 그래프")
279
+ try:
280
+ G = create_network_graph(topic_results)
281
+ html_content = create_custom_network_html(G)
282
+ components.html(html_content, height=500)
283
+ except Exception as e:
284
+ st.error(f"네트워크 그래프 생성 중 오류가 발생했습니다: {str(e)}")
285
 
 
286
  # Claude API를 사용하여 토픽 해석
287
  if api_key:
288
+ client = Anthropic(api_key=api_key)
289
+
290
  st.header("Claude의 토픽 해석")
291
  with st.spinner("토픽 해석 중..."):
292
+ prompt = f"""다음은 LDA 토픽 모델링 결과로 나온 각 토픽의 정보입니다. 이를 바탕으로 전체 토픽을 종합적으로 해석해주세요:
293
 
294
  {", ".join([f"토픽 {info['topic_num']} (비중: {info['weight']:.1f}%)" for info in topic_results])}
295
 
 
322
 
323
  위 형식에 맞춰 답변해주세요. 사용자가 쉽게 복사하여 사용할 수 있도록 간결하고 명확하게 작성해주세요."""
324
 
325
+ try:
326
+ response = client.completions.create(
327
+ model="claude-3-sonnet-20240229",
328
+ max_tokens=3000,
329
+ prompt=prompt
330
+ )
331
+ st.subheader("토픽 모델링 종합 결과")
332
+ st.text_area("결과를 복사하여 사용하세요:", value=response.completion, height=500)
333
+ except Exception as e:
334
+ st.error(f"Claude API 호출 오류가 발생했습니다: {str(e)}")
335
  else:
336
  st.warning("Claude API 키가 설정되지 않았습니다. https://console.anthropic.com/settings/keys 에 접속하여 API 키를 발급받으시면 토픽명과 해석을 제공받으실 수 있습니다.")
337