Spaces:
Running
Running
| import pandas as pd | |
| import numpy as np | |
| import io | |
| from openpyxl import Workbook | |
| from openpyxl.styles import Font, PatternFill, Alignment, Border, Side | |
| def detect_scale(series: pd.Series) -> str: | |
| try: | |
| s = series.dropna() | |
| if len(s) == 0: | |
| return "unknown" | |
| if not pd.api.types.is_numeric_dtype(series): | |
| return "categorical" | |
| u = s.unique() | |
| if len(u) <= 2: | |
| return "binary" | |
| if s.min() >= 1 and s.max() <= 7 and len(u) <= 7: | |
| return "likert" | |
| return "continuous" | |
| except Exception: | |
| return "unknown" | |
| def cronbach_alpha(data: pd.DataFrame) -> float: | |
| try: | |
| d = data.select_dtypes(include=[np.number]).dropna() | |
| k = d.shape[1] | |
| if k < 2: | |
| return np.nan | |
| iv = d.var(ddof=1, axis=0).sum() | |
| tv = d.sum(axis=1).var(ddof=1) | |
| if tv == 0: | |
| return np.nan | |
| return round(float((k / (k - 1)) * (1 - iv / tv)), 3) | |
| except Exception: | |
| return np.nan | |
| def calc_ave_cr(loadings): | |
| try: | |
| lam = np.array(loadings, dtype=float) | |
| lam2 = lam ** 2 | |
| ave = float(np.mean(lam2)) | |
| sl = float(np.sum(lam)) | |
| cr = sl**2 / (sl**2 + float(np.sum(1 - lam2))) | |
| return round(ave, 3), round(cr, 3) | |
| except Exception: | |
| return np.nan, np.nan | |
| def sig_stars(p) -> str: | |
| try: | |
| p = float(p) | |
| if p < 0.001: return "***" | |
| if p < 0.01: return "**" | |
| if p < 0.05: return "*" | |
| return "n.s." | |
| except Exception: | |
| return "-" | |
| def fmt_p(p) -> str: | |
| try: | |
| p = float(p) | |
| return "< .001" if p < 0.001 else f"{p:.3f}" | |
| except Exception: | |
| return "-" | |
| # ββ Excel μ€νμΌ ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _hdr(): | |
| return Font(name="Arial", bold=True, color="FFFFFF", size=11) | |
| def _fill(): | |
| return PatternFill("solid", fgColor="2F5496") | |
| def _bd(): | |
| return Border(left=Side(style="thin"), right=Side(style="thin"), | |
| top=Side(style="thin"), bottom=Side(style="thin")) | |
| def _ctr(): | |
| return Alignment(horizontal="center", vertical="center", wrap_text=True) | |
| def _lft(): | |
| return Alignment(horizontal="left", vertical="center") | |
| def df_to_sheet(wb: Workbook, sheet_name: str, title: str, | |
| df_data: pd.DataFrame, note: str = "", adopt_col: str = None): | |
| try: | |
| ws = wb.create_sheet(sheet_name[:31]) | |
| cols = list(df_data.columns) | |
| nc = max(len(cols), 1) | |
| # μ λͺ© | |
| ws.merge_cells(start_row=1, start_column=1, end_row=1, end_column=nc) | |
| ws["A1"] = title | |
| ws["A1"].font = Font(name="Arial", bold=True, size=12) | |
| # ν€λ | |
| for ci, col in enumerate(cols, 1): | |
| c = ws.cell(row=2, column=ci, value=str(col)) | |
| c.font = _hdr(); c.fill = _fill() | |
| c.alignment = _ctr(); c.border = _bd() | |
| # adopt_col μΈλ±μ€ | |
| adopt_idx = (cols.index(adopt_col) + 1) if adopt_col and adopt_col in cols else None | |
| # λ°μ΄ν° | |
| adopt_fill_y = PatternFill("solid", fgColor="E2EFDA") | |
| adopt_fill_n = PatternFill("solid", fgColor="FFC7CE") | |
| adopt_font_y = Font(name="Arial", size=10, color="375623") | |
| adopt_font_n = Font(name="Arial", size=10, color="9C0006") | |
| for ri, row in enumerate(df_data.itertuples(index=False), 3): | |
| for ci, val in enumerate(row, 1): | |
| safe_val = "" if (isinstance(val, float) and np.isnan(val)) else val | |
| cell = ws.cell(row=ri, column=ci, value=safe_val) | |
| cell.alignment = _lft() if ci <= 2 else _ctr() | |
| cell.border = _bd() | |
| if adopt_idx and ci == adopt_idx: | |
| is_y = str(val) == "μ±ν" | |
| cell.fill = adopt_fill_y if is_y else adopt_fill_n | |
| cell.font = adopt_font_y if is_y else adopt_font_n | |
| # μ£Όμ | |
| if note: | |
| nr = len(df_data) + 4 | |
| note_cell = ws.cell(row=nr, column=1, value=note) | |
| note_cell.font = Font(name="Arial", size=9, color="595959", italic=True) | |
| # μ΄ λλΉ μλμ‘°μ | |
| for col_cells in ws.columns: | |
| try: | |
| w = max((len(str(c.value)) if c.value else 0) for c in col_cells) | |
| ws.column_dimensions[col_cells[0].column_letter].width = min(w + 4, 45) | |
| except Exception: | |
| pass | |
| ws.row_dimensions[2].height = 28 | |
| except Exception as e: | |
| pass # μνΈ μμ± μ€ν¨ μ μ‘°μ©ν λμ΄κ° | |
| def build_excel(sheets: dict) -> bytes: | |
| wb = Workbook() | |
| wb.remove(wb.active) | |
| for name, payload in sheets.items(): | |
| try: | |
| title = payload[0] | |
| df = payload[1] | |
| note = payload[2] if len(payload) > 2 else "" | |
| adopt_col = payload[3] if len(payload) > 3 else None | |
| if df is not None and len(df) > 0: | |
| df_to_sheet(wb, name, title, df, note, adopt_col) | |
| except Exception: | |
| pass | |
| buf = io.BytesIO() | |
| wb.save(buf) | |
| return buf.getvalue() | |