import streamlit as st import pandas as pd import numpy as np import plotly.express as px import plotly.graph_objects as go from io import StringIO import openpyxl import matplotlib.font_manager as fm from scipy import stats # 한글 폰트 설정 def set_font(): font_path = "Pretendard-Bold.ttf" # 실제 폰트 파일 경로로 변경해주세요 fm.fontManager.addfont(font_path) return {'font.family': 'Pretendard-Bold', 'axes.unicode_minus': False} # 폰트 설정을 가져옵니다 font_settings = set_font() # 세션 상태 초기화 및 관리 def manage_session_state(): if 'data' not in st.session_state: st.session_state.data = None if 'processed_data' not in st.session_state: st.session_state.processed_data = None if 'numeric_columns' not in st.session_state: st.session_state.numeric_columns = [] if 'categorical_columns' not in st.session_state: st.session_state.categorical_columns = [] if 'x_var' not in st.session_state: st.session_state.x_var = None if 'y_var' not in st.session_state: st.session_state.y_var = None if 'slicers' not in st.session_state: st.session_state.slicers = {} if 'analysis_performed' not in st.session_state: st.session_state.analysis_performed = False # 데이터 로드 @st.cache_data def load_data(file): file_extension = file.name.split('.')[-1].lower() if file_extension == 'csv': data = pd.read_csv(file) elif file_extension in ['xls', 'xlsx']: data = pd.read_excel(file) else: st.error("지원되지 않는 파일 형식입니다. CSV, XLS, 또는 XLSX 파일을 업로드해주세요.") return None return data def manual_data_entry(): col_names = st.text_input("열 이름을 쉼표로 구분하여 입력하세요:", key="manual_col_names").split(',') col_names = [name.strip() for name in col_names if name.strip()] if col_names: num_rows = st.number_input("초기 행의 수를 입력하세요:", min_value=1, value=5, key="manual_num_rows") data = pd.DataFrame(columns=col_names, index=range(num_rows)) edited_data = st.data_editor(data, num_rows="dynamic", key="manual_data_editor") return edited_data return None def preprocess_data(data): # 결측치 처리 if data.isnull().sum().sum() > 0: st.write("결측치 처리:") for column in data.columns: if data[column].isnull().sum() > 0: method = st.selectbox(f"{column} 열의 처리 방법 선택:", ["제거", "평균으로 대체", "중앙값으로 대체", "최빈값으로 대체"], key=f"missing_{column}") if method == "제거": data = data.dropna(subset=[column]) elif method == "평균으로 대체": data[column].fillna(data[column].mean(), inplace=True) elif method == "중앙값으로 대체": data[column].fillna(data[column].median(), inplace=True) elif method == "최빈값으로 대체": data[column].fillna(data[column].mode()[0], inplace=True) # 데이터 타입 변환 for column in data.columns: if data[column].dtype == 'object': try: data[column] = pd.to_numeric(data[column]) st.write(f"{column} 열을 숫자형으로 변환했습니다.") except ValueError: st.write(f"{column} 열은 범주형으로 유지됩니다.") # 숫자형 열과 범주형 열 분리 st.session_state.numeric_columns = data.select_dtypes(include=['float64', 'int64']).columns.tolist() st.session_state.categorical_columns = data.select_dtypes(include=['object']).columns.tolist() return data def create_slicers(data): for col in st.session_state.categorical_columns: if data[col].nunique() <= 10: # 고유값이 10개 이하인 경우에만 슬라이서 생성 st.session_state.slicers[col] = st.multiselect( f"{col} 선택", options=sorted(data[col].unique()), default=sorted(data[col].unique()), key=f"slicer_{col}" ) def apply_slicers(data): filtered_data = data.copy() for col, selected_values in st.session_state.slicers.items(): if selected_values: filtered_data = filtered_data[filtered_data[col].isin(selected_values)] return filtered_data def plot_correlation_heatmap(data): corr = data[st.session_state.numeric_columns].corr() fig = px.imshow(corr, color_continuous_scale='RdBu_r', zmin=-1, zmax=1) fig.update_layout(title='상관관계 히트맵') st.plotly_chart(fig) def plot_scatter_with_regression(data, x_var, y_var): fig = px.scatter(data, x=x_var, y=y_var, color='반' if '반' in data.columns else None) # 회귀선 추가 x = data[x_var] y = data[y_var] slope, intercept, r_value, p_value, std_err = stats.linregress(x, y) line_x = np.array([x.min(), x.max()]) line_y = slope * line_x + intercept fig.add_trace(go.Scatter(x=line_x, y=line_y, mode='lines', name='회귀선')) r_squared = r_value ** 2 fig.update_layout( title=f'{x_var}와 {y_var}의 관계 (R-squared: {r_squared:.4f})', xaxis_title=x_var, yaxis_title=y_var, annotations=[ dict( x=0.5, y=1.05, xref='paper', yref='paper', text=f'R-squared: {r_squared:.4f}', showarrow=False, ) ] ) st.plotly_chart(fig) # 추가 통계 정보 st.write(f"상관계수: {r_value:.4f}") st.write(f"p-value: {p_value:.4f}") st.write(f"표준 오차: {std_err:.4f}") def perform_analysis(): if st.session_state.processed_data is not None and not st.session_state.processed_data.empty: st.header("탐색적 데이터 분석") # 슬라이서 생성 및 적용 create_slicers(st.session_state.processed_data) filtered_data = apply_slicers(st.session_state.processed_data) # 요약 통계 st.write("요약 통계:") st.write(filtered_data.describe()) # 상관관계 히트맵 st.subheader("상관관계 히트맵") plot_correlation_heatmap(filtered_data) # 사용자가 선택한 두 변수에 대한 산점도 및 회귀 분석 st.subheader("두 변수 간의 관계 분석") st.session_state.x_var = st.selectbox("X축 변수 선택", options=st.session_state.numeric_columns, key='x_var') st.session_state.y_var = st.selectbox("Y축 변수 선택", options=[col for col in st.session_state.numeric_columns if col != st.session_state.x_var], key='y_var') if st.session_state.x_var and st.session_state.y_var: plot_scatter_with_regression(filtered_data, st.session_state.x_var, st.session_state.y_var) st.session_state.analysis_performed = True else: st.warning("분석할 데이터가 없습니다. 데이터를 먼저 로드하고 전처리해주세요.") # state 유지하도록 추가 def update_filtered_data(): st.session_state.filtered_data = apply_slicers(st.session_state.processed_data) def create_slicers(data): for col in st.session_state.categorical_columns: if data[col].nunique() <= 10: st.session_state.slicers[col] = st.multiselect( f"{col} 선택", options=sorted(data[col].unique()), default=sorted(data[col].unique()), key=f"slicer_{col}", on_change=update_filtered_data ) def apply_slicers(data): filtered_data = data.copy() for col, selected_values in st.session_state.slicers.items(): if selected_values: filtered_data = filtered_data[filtered_data[col].isin(selected_values)] return filtered_data def perform_analysis(): if 'filtered_data' not in st.session_state or st.session_state.filtered_data is None: st.session_state.filtered_data = st.session_state.processed_data.copy() st.header("탐색적 데이터 분석") # 슬라이서 생성 create_slicers(st.session_state.processed_data) # 요약 통계 st.write("요약 통계:") st.write(st.session_state.filtered_data.describe()) # 상관관계 히트맵 st.subheader("상관관계 히트맵") plot_correlation_heatmap(st.session_state.filtered_data) # 사용자가 선택한 두 변수에 대한 산점도 및 회귀 분석 st.subheader("두 변수 간의 관계 분석") x_var = st.selectbox("X축 변수 선택", options=st.session_state.numeric_columns, key='x_var') y_var = st.selectbox("Y축 변수 선택", options=[col for col in st.session_state.numeric_columns if col != x_var], key='y_var') if x_var and y_var: plot_scatter_with_regression(st.session_state.filtered_data, x_var, y_var) def main(): st.title("인터랙티브 EDA 툴킷") manage_session_state() if 'data' not in st.session_state or st.session_state.data is None: # ... (데이터 로드 부분) if st.session_state.data is not None: st.subheader("데이터 미리보기 및 수정") st.write("데이터를 확인하고 필요한 경우 수정하세요:") edited_data = st.data_editor(st.session_state.data, num_rows="dynamic", key="data_editor") if st.button("데이터 분석 시작", key="start_analysis") or ('analysis_performed' in st.session_state and st.session_state.analysis_performed): if 'analysis_performed' not in st.session_state or not st.session_state.analysis_performed: st.session_state.processed_data = preprocess_data(edited_data) st.session_state.analysis_performed = True perform_analysis() if __name__ == "__main__": main()