import streamlit as st import pandas as pd import numpy as np st.set_page_config(page_title="SPK Lokasi Kedai Kopi", layout="wide") st.title("📊 Sistem Pendukung Keputusan Lokasi Kedai Kopi") st.markdown("Metode: **AHP**, **TOPSIS**, dan **Profile Matching**") # --- Step 1: User Inputs for Criteria and Alternatives --- st.sidebar.header("⚙️ Pengaturan") criteria = st.sidebar.text_area("Masukkan Kriteria (pisahkan dengan koma)", "Building Area, Road Access, Distance, Rental Price") criteria = [c.strip() for c in criteria.split(",") if c.strip()] alternatives = st.sidebar.text_area("Masukkan Alternatif (pisahkan dengan koma)", "Location 1, Location 2, Location 3, Location 4") alternatives = [a.strip() for a in alternatives.split(",") if a.strip()] if not criteria or not alternatives: st.warning("Masukkan minimal satu kriteria dan alternatif.") st.stop() # --- Step 2: Input Matrix (User-Editable) --- st.subheader("📋 Input Nilai Alternatif terhadap Kriteria") empty_data = pd.DataFrame(np.nan, index=alternatives, columns=criteria) df = st.data_editor(empty_data, use_container_width=True, key="input_matrix") # Validasi isian if df.isnull().values.any(): st.warning("⚠️ Harap lengkapi semua nilai pada tabel sebelum menjalankan perhitungan.") st.stop() if method == "AHP": st.subheader("🔗 Perbandingan Berpasangan Antar Kriteria (AHP)") pairwise_matrix = pd.DataFrame(np.ones((len(criteria), len(criteria))), index=criteria, columns=criteria) for i in range(len(criteria)): for j in range(i + 1, len(criteria)): val = st.number_input( f"Seberapa penting '{criteria[i]}' dibandingkan '{criteria[j]}'?", min_value=1/9.0, max_value=9.0, value=1.0, step=0.1, key=f"{criteria[i]}_{criteria[j]}" ) pairwise_matrix.iloc[i, j] = val pairwise_matrix.iloc[j, i] = 1 / val st.write("📊 Matriks Perbandingan Kriteria:") st.dataframe(pairwise_matrix) norm_matrix = pairwise_matrix / pairwise_matrix.sum() weights = norm_matrix.mean(axis=1).values weights /= weights.sum() st.write("🎯 Bobot Kriteria dari AHP:") st.dataframe(pd.Series(weights, index=criteria, name="Bobot")) else: # --- Step 3 (non-AHP): Manual Bobot Kriteria --- st.subheader("⚖️ Bobot Kriteria") weight_dict = {} cols = st.columns(len(criteria)) for i, c in enumerate(criteria): with cols[i]: weight_dict[c] = st.number_input(f"Bobot untuk '{c}'", min_value=1.0, max_value=10.0, value=5.0, step=0.1) weights = np.array([weight_dict[c] for c in criteria]) weights /= weights.sum() # normalize # --- Step 4: Ideal Profile --- st.subheader("🎯 Ideal Profile (untuk Profile Matching)") ideal_profile_dict = {} cols = st.columns(len(criteria)) for i, c in enumerate(criteria): with cols[i]: ideal_profile_dict[c] = st.number_input(f"Ideal '{c}' (1-5)", min_value=1, max_value=5, value=5) ideal_profile = pd.Series(ideal_profile_dict) # --- Step 5: Metode --- st.subheader("🧠 Pilih Metode") method = st.selectbox("Metode yang ingin dijalankan", ["AHP", "TOPSIS", "Profile Matching"]) # --- UTILITIES --- def scale_to_five(val): if val >= 90: return 5 elif val >= 80: return 4 elif val >= 70: return 3 elif val >= 60: return 2 elif val >= 50: return 1 else: return 1 # --- ALGORITHMS --- def run_ahp(df, weights): normalized = df / df.sum() weighted = normalized * weights return weighted.sum(axis=1) def run_topsis(df, weights, types): norm_df = df / np.sqrt((df**2).sum()) weighted = norm_df * weights ideal_pos = np.where(types == 1, weighted.max(), weighted.min()) ideal_neg = np.where(types == 1, weighted.min(), weighted.max()) d_pos = np.sqrt(((weighted - ideal_pos)**2).sum(axis=1)) d_neg = np.sqrt(((weighted - ideal_neg)**2).sum(axis=1)) return d_neg / (d_pos + d_neg) def run_profile_matching(df_scaled, ideal, cf_weight, sf_weight, cf_indices): gap_weights = { 0: 5, 1: 4.5, -1: 4.5, 2: 4, -2: 4, 3: 3.5, -3: 3.5, 4: 3, -4: 3, 5: 2.5, -5: 2.5 } df_gap = df_scaled - ideal df_wgap = df_gap.applymap(lambda x: gap_weights.get(int(x), 0)) cf_cols = df_scaled.columns[cf_indices] sf_cols = df_scaled.columns.drop(cf_cols) ncf = df_wgap[cf_cols].mean(axis=1) nsf = df_wgap[sf_cols].mean(axis=1) return cf_weight * ncf + sf_weight * nsf # --- Step 6: Run --- if st.button("🔍 Jalankan Perhitungan"): df_raw = df.copy() st.write("📊 Data Masukan", df_raw) if method == "AHP": st.subheader("📈 Hasil AHP") result = run_ahp(df_raw, weights) st.dataframe(result.rename("Skor").sort_values(ascending=False)) elif method == "TOPSIS": st.subheader("📈 Hasil TOPSIS") types = np.array([1 if "price" not in c.lower() else -1 for c in criteria]) result = run_topsis(df_raw, weights, types) st.dataframe(result.rename("Skor").sort_values(ascending=False)) elif method == "Profile Matching": st.subheader("📈 Hasil Profile Matching") df_scaled = df_raw.applymap(scale_to_five) st.write("📏 Data Skala 1–5", df_scaled) core_factors = [criteria[-1]] # default: last one is core (e.g., Rental Price) cf_indices = [criteria.index(c) for c in core_factors] result = run_profile_matching(df_scaled, ideal_profile, cf_weight=0.6, sf_weight=0.4, cf_indices=cf_indices) st.dataframe(result.rename("Skor").sort_values(ascending=False))