soojeongcrystal commited on
Commit
5f4f426
·
verified ·
1 Parent(s): 65311a6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +479 -1
app.py CHANGED
@@ -476,6 +476,484 @@ if 'run_analysis' in st.session_state and st.session_state.run_analysis:
476
  except Exception as e:
477
  return f"Claude API 호출 중 오류가 발생했습니다: {str(e)}"
478
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
479
  if st.session_state.analysis_type == "전체 분석":
480
  col1, col2 = st.columns([3, 1])
481
  with col2:
@@ -522,4 +1000,4 @@ if st.button("분석 초기화"):
522
  st.markdown("""
523
  ---
524
  © 2024 SK mySUNI 행복 College. All rights reserved. 문의사항이 있으시면 연락주세요 (배수정RF, soojeong.bae@sk.com)
525
- """)
 
476
  except Exception as e:
477
  return f"Claude API 호출 중 오류가 발생했습니다: {str(e)}"
478
 
479
+ import streamlit as st
480
+ import pandas as pd
481
+ import numpy as np
482
+ from anthropic import Anthropic, HUMAN_PROMPT, AI_PROMPT
483
+ import json
484
+ from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
485
+ from sklearn.decomposition import LatentDirichletAllocation
486
+ from konlpy.tag import Okt
487
+ import re
488
+ import os
489
+ import altair as alt
490
+ import colorsys
491
+ import matplotlib.pyplot as plt
492
+ import matplotlib.font_manager as fm
493
+ import networkx as nx
494
+ import io
495
+
496
+ # Streamlit 페이지 설정
497
+ st.set_page_config(layout="wide", page_title="📊 토픽모델링 for SK", page_icon="📊")
498
+
499
+ # KoNLPy 형태소 분석기 초기화
500
+ @st.cache_resource
501
+ def load_okt():
502
+ return Okt()
503
+
504
+ okt = load_okt()
505
+
506
+ # 기본 불용어 목록
507
+ default_stop_words = ['이', '그', '저', '것', '수', '등', '들', '및', '에서', '그리고', '그래서', '또는', '그런데', '의', '대한', '간의']
508
+
509
+ @st.cache_data
510
+ def preprocess_text(text, stop_words):
511
+ text = re.sub(r'[^가-힣\s]', '', str(text))
512
+ nouns = okt.nouns(text)
513
+ processed = [word for word in nouns if word not in stop_words and len(word) > 1]
514
+ return ' '.join(processed)
515
+
516
+ def generate_colors(n):
517
+ HSV_tuples = [(x * 1.0 / n, 0.5, 0.9) for x in range(n)]
518
+ return ['#%02x%02x%02x' % tuple(int(x*255) for x in colorsys.hsv_to_rgb(*hsv)) for hsv in HSV_tuples]
519
+
520
+ def create_network_graph(topic_results, num_words=10):
521
+ G = nx.Graph()
522
+ colors = generate_colors(len(topic_results))
523
+
524
+ for idx, topic in enumerate(topic_results):
525
+ words = topic['lda_words'][:num_words]
526
+ color = colors[idx]
527
+
528
+ for word in words:
529
+ if not G.has_node(word):
530
+ G.add_node(word, color=color)
531
+
532
+ for i in range(len(words)):
533
+ for j in range(i+1, len(words)):
534
+ if not G.has_edge(words[i], words[j]):
535
+ G.add_edge(words[i], words[j])
536
+
537
+ return G
538
+
539
+ def plot_network_graph(G):
540
+ font_path = "./NanumBarunGothic.ttf"
541
+ font_prop = fm.FontProperties(fname=font_path)
542
+ plt.rcParams['font.family'] = font_prop.get_name()
543
+
544
+ plt.figure(figsize=(12, 8))
545
+
546
+ pos = nx.spring_layout(G, k=0.5, iterations=50, seed=42)
547
+
548
+ node_colors = [G.nodes[node]['color'] for node in G.nodes()]
549
+
550
+ nx.draw(G, pos, node_color=node_colors, with_labels=False, node_size=1000,
551
+ edge_color='gray', width=0.5)
552
+
553
+ for node, (x, y) in pos.items():
554
+ plt.text(x, y, node, fontsize=8, ha='center', va='center',
555
+ fontproperties=font_prop, fontweight='bold',
556
+ bbox=dict(facecolor='white', edgecolor='none', alpha=0.7))
557
+
558
+ plt.title("토픽 단어 네트워크", fontsize=16, fontproperties=font_prop)
559
+ plt.axis('off')
560
+
561
+ img_bytes = io.BytesIO()
562
+ plt.savefig(img_bytes, format='png', dpi=300, bbox_inches='tight')
563
+ img_bytes.seek(0)
564
+ plt.close()
565
+
566
+ return img_bytes
567
+
568
+ def perform_topic_modeling(df, text_column, num_topics, stop_words):
569
+ df['processed_text'] = df[text_column].apply(lambda x: preprocess_text(x, stop_words))
570
+
571
+ vectorizer = CountVectorizer(max_df=0.95, min_df=2)
572
+ doc_term_matrix = vectorizer.fit_transform(df['processed_text'])
573
+
574
+ lda = LatentDirichletAllocation(n_components=num_topics, random_state=42)
575
+ lda_output = lda.fit_transform(doc_term_matrix)
576
+
577
+ tfidf_vectorizer = TfidfVectorizer(max_df=0.95, min_df=2)
578
+ tfidf_matrix = tfidf_vectorizer.fit_transform(df['processed_text'])
579
+
580
+ feature_names = vectorizer.get_feature_names_out()
581
+
582
+ topic_results = []
583
+
584
+ for idx, topic in enumerate(lda.components_):
585
+ # LDA 상위 단어를 점수 순으로 정렬
586
+ lda_top_words = sorted([(feature_names[i], topic[i]) for i in range(len(topic))], key=lambda x: x[1], reverse=True)[:10]
587
+ topic_docs = lda_output[:, idx].argsort()[::-1][:100]
588
+ topic_tfidf = tfidf_matrix[topic_docs].mean(axis=0).A1
589
+ # TF-IDF 상위 단어도 점수 순으로 정렬
590
+ tfidf_top_words = sorted([(feature_names[i], topic_tfidf[i]) for i in range(len(topic_tfidf))], key=lambda x: x[1], reverse=True)[:10]
591
+ weight = lda_output[:, idx].mean() * 100
592
+
593
+ topic_name = ", ".join([word for word, _ in lda_top_words[:5]])
594
+
595
+ topic_results.append({
596
+ 'topic_num': idx + 1,
597
+ 'topic_name': topic_name,
598
+ 'lda_words': [word for word, _ in lda_top_words],
599
+ 'tfidf_words': [word for word, _ in tfidf_top_words],
600
+ 'weight': weight
601
+ })
602
+
603
+ return topic_results, lda, lda_output, tfidf_matrix, feature_names
604
+
605
+ def perform_conditional_analysis(df, condition_column, text_column, num_topics, stop_words, is_numeric, condition):
606
+ if is_numeric:
607
+ if isinstance(condition, tuple) and condition[0] == "이상":
608
+ filtered_df = df[df[condition_column] >= condition[1]]
609
+ elif isinstance(condition, tuple) and condition[0] == "이하":
610
+ filtered_df = df[df[condition_column] <= condition[1]]
611
+ else: # 범위 선택
612
+ filtered_df = df[(df[condition_column] >= condition[0]) & (df[condition_column] <= condition[1])]
613
+ topic_results, _, _, _, _ = perform_topic_modeling(filtered_df, text_column, num_topics, stop_words)
614
+ return {f"{condition_column} {condition}": topic_results}
615
+ else:
616
+ results = {}
617
+ for value in condition:
618
+ filtered_df = df[df[condition_column] == value]
619
+ if len(filtered_df) > 0:
620
+ topic_results, _, _, _, _ = perform_topic_modeling(filtered_df, text_column, num_topics, stop_words)
621
+ results[value] = topic_results
622
+ return results
623
+
624
+ # 스타일 설정
625
+ st.markdown("""
626
+ <style>
627
+ .css-1adrfps {
628
+ padding: 0px;
629
+ }
630
+ .css-1kyxreq {
631
+ padding: 10px;
632
+ }
633
+ .topic-summary {
634
+ background-color: #f0f2f6;
635
+ border-left: 5px solid #4e8098;
636
+ padding: 10px;
637
+ margin-bottom: 10px;
638
+ }
639
+ .small-title {
640
+ font-size: 24px;
641
+ }
642
+ .header-text {
643
+ color: #707070;
644
+ text-align: right;
645
+ width: 100%;
646
+ padding: 10px;
647
+ }
648
+ </style>
649
+ <div class="header-text">
650
+ mySUNI Crystal.B
651
+ </div>
652
+ """, unsafe_allow_html=True)
653
+
654
+ st.markdown('<h1 class="small-title">📊토픽모델링 for SK</h1>', unsafe_allow_html=True)
655
+
656
+ # 사이드바 설정
657
+ with st.sidebar:
658
+ st.header('설정하기')
659
+
660
+ api_key = st.text_input("Claude API 키를 입력하세요", type="password")
661
+ if not api_key:
662
+ api_key = os.environ.get("ANTHROPIC_API_KEY")
663
+
664
+ st.caption("Claude API가 있으면 토픽 종합 해석까지 가능합니다. 공백으로 두면 기본적인 결과만 나옵니다.")
665
+
666
+ stop_words_input = st.text_area("불용어 목록 (쉼표로 구분)", ', '.join(default_stop_words))
667
+ stop_words = [word.strip() for word in stop_words_input.split(',') if word.strip()]
668
+
669
+ st.caption("결과를 보고 업데이트해주세요.")
670
+
671
+ uploaded_file = st.file_uploader("CSV 파일을 업로드하세요", type="csv")
672
+
673
+ st.caption("csv-UTF형식을 사용해주세요!")
674
+
675
+ # 데이터 로드 및 초기 설정
676
+ if uploaded_file is not None:
677
+ try:
678
+ df = pd.read_csv(uploaded_file)
679
+ if df.empty:
680
+ st.error("CSV 파일에 데이터가 없습니다.")
681
+ else:
682
+ st.success("파일이 성공적으로 업로드되었습니다.")
683
+ st.subheader("데이터 미리보기")
684
+ st.write(df.head())
685
+
686
+ text_column = st.selectbox("텍스트 컬럼을 선택하세요", df.columns)
687
+ num_topics = st.slider("토픽 수를 선택하세요", 2, 20, 5)
688
+
689
+ # 분석 방법 선택
690
+ analysis_type = st.radio("분석 방법 선택", ["전체 분석", "조건부 분석"])
691
+
692
+ if analysis_type == "조건부 분석":
693
+ condition_column = st.selectbox("조건부 분석에 사용할 변수를 선택하세요", df.columns)
694
+
695
+ if pd.api.types.is_numeric_dtype(df[condition_column]):
696
+ min_val, max_val = df[condition_column].min(), df[condition_column].max()
697
+ st.write(f"{condition_column}의 범위: {min_val:.2f} ~ {max_val:.2f}")
698
+
699
+ analysis_method = st.radio("분석 방법 선택", ["범위 선택", "임계값 기준"])
700
+
701
+ if analysis_method == "범위 선택":
702
+ condition = st.slider(f"{condition_column} 범위 선택", float(min_val), float(max_val), (float(min_val), float(max_val)))
703
+ else: # 임계값 기준
704
+ threshold = st.number_input(f"{condition_column} 임계값 설정", min_value=float(min_val), max_value=float(max_val), value=float((min_val + max_val) / 2))
705
+ comparison = st.radio("비교 기준", ["이상", "이하"])
706
+ condition = (comparison, threshold)
707
+
708
+ is_numeric = True
709
+ else:
710
+ unique_values = df[condition_column].unique()
711
+ condition = st.multiselect(f"{condition_column} 값 선택", unique_values, default=unique_values)
712
+ is_numeric = False
713
+
714
+ if st.button("토픽 모델링 실행"):
715
+ st.session_state.run_analysis = True
716
+ st.session_state.text_column = text_column
717
+ st.session_state.num_topics = num_topics
718
+ st.session_state.analysis_type = analysis_type
719
+ if analysis_type == "조건부 분석":
720
+ st.session_state.condition_column = condition_column
721
+ st.session_state.condition = condition
722
+ st.session_state.is_numeric = is_numeric
723
+ else:
724
+ st.session_state.run_analysis = False
725
+ except pd.errors.EmptyDataError:
726
+ st.error("업로드된 CSV 파일이 비어있습니다. 다시 확인해주세요.")
727
+ except UnicodeDecodeError:
728
+ st.error("파일 인코딩에 문제가 있습니다. UTF-8 인코딩으로 저장된 CSV 파일을 사용해주세요.")
729
+ except Exception as e:
730
+ st.error(f"파일을 읽는 중 오류가 발생했습니다: {str(e)}")
731
+ else:
732
+ st.info("CSV 파일을 업로드해주세요.")
733
+
734
+ # 메인 분석 로직
735
+ if 'run_analysis' in st.session_state and st.session_state.run_analysis:
736
+ if 'text_column' in st.session_state and 'num_topics' in st.session_state and 'analysis_type' in st.session_state:
737
+ try:
738
+ if st.session_state.analysis_type == "전체 분석":
739
+ st.header("전체 데이터 분석 결과")
740
+
741
+ with st.spinner("토픽 모델링 실행 중..."):
742
+ topic_results, lda, lda_output, tfidf_matrix, feature_names = perform_topic_modeling(
743
+ df, st.session_state.text_column, st.session_state.num_topics, stop_words
744
+ )
745
+
746
+ # 토픽 요약 표시
747
+ topic_summary = ", ".join([f"토픽{info['topic_num']}({info['topic_name']}, {info['weight']:.1f}%)" for info in topic_results])
748
+ st.markdown(f'<div class="topic-summary">{topic_summary}</div>', unsafe_allow_html=True)
749
+
750
+ # 토픽별 상세 정보 표시
751
+ for idx, topic_info in enumerate(topic_results):
752
+ st.subheader(f"토픽 {idx + 1}")
753
+
754
+ col1, col2 = st.columns(2)
755
+
756
+ with col1:
757
+ df_lda = pd.DataFrame(list(zip(topic_info['lda_words'], lda.components_[idx][np.argsort(lda.components_[idx])[::-1][:10]])),
758
+ columns=['단어', 'LDA 점수'])
759
+ st.subheader("LDA 상위 단어")
760
+ st.table(df_lda.style.format({'LDA 점수': '{:.4f}'}))
761
+
762
+ with col2:
763
+ df_tfidf = pd.DataFrame(list(zip(topic_info['tfidf_words'],
764
+ tfidf_matrix[lda_output[:, idx].argsort()[::-1][:100]].mean(axis=0).A1[np.argsort(tfidf_matrix[lda_output[:, idx].argsort()[::-1][:100]].mean(axis=0).A1)[::-1][:10]])),
765
+ columns=['단어', 'TF-IDF'])
766
+ st.subheader("TF-IDF 상위 단어")
767
+ st.table(df_tfidf.style.format({'TF-IDF': '{:.4f}'}))
768
+
769
+ # 토픽 비중 그래프
770
+ st.header("토픽 비중 그래프")
771
+ df_weights = pd.DataFrame({
772
+ '토픽': [f'토픽 {i+1}' for i in range(st.session_state.num_topics)],
773
+ '비중': [result['weight'] for result in topic_results]
774
+ })
775
+
776
+ colors = generate_colors(st.session_state.num_topics)
777
+
778
+ chart = alt.Chart(df_weights).mark_bar().encode(
779
+ x=alt.X('토픽:N', axis=alt.Axis(labelAngle=0)),
780
+ y=alt.Y('비중:Q', axis=alt.Axis(format=',.1f')),
781
+ color=alt.Color('토픽:N', scale=alt.Scale(range=colors))
782
+ ).properties(
783
+ width=600,
784
+ height=400,
785
+ title='문서 내 토픽 비중 (%)'
786
+ )
787
+
788
+ text = chart.mark_text(
789
+ align='center',
790
+ baseline='bottom',
791
+ dy=-5
792
+ ).encode(
793
+ text=alt.Text('비중:Q', format='.1f')
794
+ )
795
+
796
+ st.altair_chart(chart + text, use_container_width=True)
797
+
798
+ # 네트워크 그래프
799
+ st.header("토픽 단어 네트워크 그래프")
800
+ G = create_network_graph(topic_results, num_words=20)
801
+ img_bytes = plot_network_graph(G)
802
+ st.image(img_bytes, caption="토픽별 상위 20개 단어 네트워크", use_column_width=True)
803
+
804
+ st.download_button(
805
+ label="네트워크 그래프 다운로드",
806
+ data=img_bytes,
807
+ file_name="topic_network_graph.png",
808
+ mime="image/png",
809
+ key="download_graph"
810
+ )
811
+
812
+ # 토픽 요약 테이블
813
+ st.subheader("토픽 요약 테이블")
814
+ topic_summary_df = pd.DataFrame([
815
+ {
816
+ '토픽 번호': f"토픽{info['topic_num']}",
817
+ '비중': f"{info['weight']:.1f}%",
818
+ 'LDA 상위 단어': ", ".join(info['lda_words'][:10]),
819
+ 'TF-IDF 상위 단어': ", ".join(info['tfidf_words'][:10])
820
+ } for info in topic_results
821
+ ])
822
+ st.table(topic_summary_df)
823
+
824
+ elif st.session_state.analysis_type == "조건부 분석":
825
+ st.header("조건부 분석 결과")
826
+
827
+ with st.spinner("조건부 토픽 모델링 실행 중..."):
828
+ conditional_results = perform_conditional_analysis(
829
+ df, st.session_state.condition_column, st.session_state.text_column,
830
+ st.session_state.num_topics, stop_words, st.session_state.is_numeric,
831
+ st.session_state.condition
832
+ )
833
+
834
+ for value, topic_results in conditional_results.items():
835
+ st.subheader(f"{st.session_state.condition_column}: {value}")
836
+
837
+ # 토픽 요약 표시
838
+ topic_summary = ", ".join([f"토픽{info['topic_num']}({info['topic_name']}, {info['weight']:.1f}%)" for info in topic_results])
839
+ st.markdown(f'<div class="topic-summary">{topic_summary}</div>', unsafe_allow_html=True)
840
+
841
+ # 토픽 요약 테이블
842
+ topic_summary_df = pd.DataFrame([
843
+ {
844
+ '토픽 번호': f"토픽{info['topic_num']}",
845
+ '비중': f"{info['weight']:.1f}%",
846
+ 'LDA 상위 단어': ", ".join(info['lda_words'][:10]),
847
+ 'TF-IDF 상위 단어': ", ".join(info['tfidf_words'][:10])
848
+ } for info in topic_results
849
+ ])
850
+ st.table(topic_summary_df)
851
+
852
+ # 네트워크 그래프
853
+ st.subheader(f"{value} - 토픽 단어 네트워크 그래프")
854
+ G = create_network_graph(topic_results, num_words=20)
855
+ img_bytes = plot_network_graph(G)
856
+ st.image(img_bytes, caption=f"{value} - 토픽별 상위 20개 단어 네트워크", use_column_width=True)
857
+
858
+ st.download_button(
859
+ label=f"{value} - 네트워크 그래프 다운로드",
860
+ data=img_bytes,
861
+ file_name=f"topic_network_graph_{value}.png",
862
+ mime="image/png",
863
+ key=f"download_graph_{value}"
864
+ )
865
+
866
+ st.markdown("---") # 각 카테고리 결과 사이에 구분선 추가
867
+
868
+ # Claude API를 사용한 토픽 해석 부분
869
+ if api_key:
870
+ st.header("토픽 종합 해석")
871
+
872
+ def interpret_topics_full(api_key, topic_results):
873
+ anthropic = Anthropic(api_key=api_key)
874
+
875
+ prompt = f"""다음은 LDA 토픽 모델링 결과로 나온 각 토픽의 정보입니다. 이를 바탕으로 전체 토픽을 종합적으로 해석해주세요:
876
+
877
+ {", ".join([f"토픽 {{info['topic_num']}} (비중: {{info['weight']:.1f}}%)" for info in topic_results])}
878
+ 각 토픽의 주요 단어:
879
+ """
880
+ for info in topic_results:
881
+ prompt += f"""
882
+ 토픽 {info['topic_num']} (비중: {info['weight']:.1f}%):
883
+ LDA 상위 단어: {', '.join(info['lda_words'][:10])}
884
+ TF-IDF 상위 단어: {', '.join(info['tfidf_words'][:10])}
885
+ """
886
+
887
+ prompt += """
888
+ 위 정보를 바탕으로 다음 형식에 맞춰 답변해주세요:
889
+ 1. 전체 문서의 주제 요약 (3-4문장):
890
+ [여기에 전체 문서의 주제를 종합적으로 설명해주세요. 각 토픽의 비중을 고려하여 중요도를 반영해주세요.]
891
+ 2. 각 토픽 요약:
892
+ [각 토픽에 대해 다음 형식으로 요약해주세요]
893
+ 토픽[번호] "[토픽명]" [비중]%
894
+ • LDA 상위 단어 10개: [LDA 상위 단어 10개를 쉼표로 구분하여 나열]
895
+ • TF-IDF 상위 단어 10개: [TF-IDF 상위 단어 10개를 쉼표로 구분하여 나열]
896
+ • 토픽명 설명: [토픽명이 이렇게 지어진 이유를 1-2문장으로 설명해주세요. LDA와 TF-IDF 상위 단어들이 어떻게 이 토픽명과 연관되는지 설명하세요.]
897
+ • 토픽 설명: [2-3문장으로 토픽의 전반적인 내용을 설명해주세요.]
898
+ 주의사항:
899
+ 1. 토픽명은 "[구체적인 토픽명]" 형식으로 작성해주세요. 반드시 8어절 이상으로 구체적이고 설명적으로 작성해야 합니다.
900
+ 2. 예시: "구성원의 전문성 향상을 위한 체계적인 학습과 역량 개발 방안 모색", "조직의 장기적 성과 향상을 위한 핵심 학습 역량 강화 전략",
901
+ "현재 컬리지 멤버들의 역할 고민과 향후 발전 방향에 대한 논의" 등
902
+ 3. 토픽명은 단순히 단어를 나열하는 것이 아니라, 토픽의 핵심 주제나 의미를 잘 나타내는 구체적이고 설명적인 문구로 만들어주세요.
903
+ 4. 각 토픽의 LDA 상위 단어와 TF-IDF 상위 단어 10개를 반드시 포함해주세요.
904
+ 위 형식에 맞춰 답변해주세요. 사용자가 쉽게 복사하여 사용할 수 있도록 간결하고 명확하게 작성해주세요.
905
+ """
906
+
907
+ try:
908
+ completion = anthropic.completions.create(
909
+ model="claude-2.1",
910
+ max_tokens_to_sample=4000,
911
+ prompt=f"{HUMAN_PROMPT} {prompt} {AI_PROMPT}",
912
+ )
913
+ return completion.completion
914
+ except Exception as e:
915
+ return f"Claude API 호출 중 오류가 발생했습니다: {str(e)}"
916
+
917
+ def interpret_topics_conditional(api_key, topic_results, condition):
918
+ anthropic = Anthropic(api_key=api_key)
919
+
920
+ prompt = f"""다음은 LDA 토픽 모델링 결과로 나온 각 토픽의 정보입니다. 이를 바탕으로 각 토픽에 대해 간략히 요약해주세요:
921
+
922
+ 조건: {condition}
923
+ {", ".join([f"토픽 {{info['topic_num']}} (비중: {{info['weight']:.1f}}%)" for info in topic_results])}
924
+ 각 토픽의 주요 단어:
925
+ """
926
+ for info in topic_results:
927
+ prompt += f"""
928
+ 토픽 {info['topic_num']} (비중: {info['weight']:.1f}%):
929
+ LDA 상위 단어: {', '.join(info['lda_words'][:10])}
930
+ TF-IDF 상위 단어: {', '.join(info['tfidf_words'][:10])}
931
+ """
932
+
933
+ prompt += """
934
+ 위 정보를 바탕으로 다음 형식에 맞춰 답변해주세요:
935
+ 각 토픽 요약:
936
+ 토픽[번호] "[토픽명]" [비중]%
937
+
938
+ 주의사항:
939
+ 1. 토픽명은 "[구체적인 토픽명]" 형식으로 작성해주세요. 반드시 8어절 이상으로 구체적이고 설명적으로 작성해야 합니다.
940
+ 2. 예시: "구성원의 전문성 향상을 위한 체계적인 학습과 역량 개발 방안 모색", "조직의 장기적 성과 향상을 위한 핵심 학습 역량 강화 전략",
941
+ "현재 컬리지 멤버들의 역할 고민과 향후 발전 방향에 대한 논의" 등
942
+ 3. 토픽명은 단순히 단어를 나열하는 것이 아니라, 토픽의 핵심 주제나 의미를 잘 나타내는 구체적이고 설명적인 문구로 만들어주세요.
943
+
944
+ 위 형식에 맞춰 각 토픽에 대해 간략히 요약해주세요. 사용자가 쉽게 복사하여 사용할 수 있도록 간결하고 명확하게 작성해주세요.
945
+ """
946
+
947
+ try:
948
+ completion = anthropic.completions.create(
949
+ model="claude-2.1",
950
+ max_tokens_to_sample=2000,
951
+ prompt=f"{HUMAN_PROMPT} {prompt} {AI_PROMPT}",
952
+ )
953
+ return completion.completion
954
+ except Exception as e:
955
+ return f"Claude API 호출 중 오류가 발생했습니다: {str(e)}"
956
+
957
  if st.session_state.analysis_type == "전체 분석":
958
  col1, col2 = st.columns([3, 1])
959
  with col2:
 
1000
  st.markdown("""
1001
  ---
1002
  © 2024 SK mySUNI 행복 College. All rights reserved. 문의사항이 있으시면 연락주세요 (배수정RF, soojeong.bae@sk.com)
1003
+ """)