File size: 5,284 Bytes
26c3195 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | 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()
|