Spaces:
Running
Running
merapikan file, menambahkan komentar penting pada config.py, preprocessing.py, visualization.py, dan app.py
9e73d6d
verified
| """ | |
| Modul visualization.py | |
| ---------------------- | |
| Berisi fungsi-fungsi untuk menampilkan berbagai visualisasi data kritik dan saran | |
| dalam bentuk bar chart, pie chart, serta distribusi berdasarkan tahun, semester, | |
| program studi, dan mata kuliah menggunakan Streamlit & Plotly. | |
| UPDATED: Visualisasi dinamis yang menyesuaikan dengan kolom yang tersedia | |
| """ | |
| import streamlit as st | |
| import pandas as pd | |
| import plotly.express as px | |
| from config import ASPEK_COLUMNS | |
| # Palet warna kustom untuk setiap kategori sentimen | |
| sentimen_palette = { | |
| "netral": "#FFE24C", # Kuning untuk netral | |
| "positif": "#4CFF72", # Hijau untuk positif | |
| "negatif": "#FF4C4C" # Merah untuk negatif | |
| } | |
| # Urutan kategori sentimen untuk konsistensi visualisasi | |
| category_order = ["netral", "positif", "negatif"] | |
| # Konfigurasi Plotly untuk interaktivitas chart | |
| config_options = { | |
| "scrollZoom": False, # Nonaktifkan zoom dengan scroll | |
| "displayModeBar": False # Sembunyikan toolbar Plotly | |
| } | |
| def show_sentiment_bar_chart(df_predicted, aspek_columns): | |
| """ | |
| Menampilkan bar chart distribusi sentimen per aspek. | |
| Chart menampilkan jumlah setiap sentimen (positif/netral/negatif) untuk setiap aspek. | |
| Args: | |
| df_predicted (pd.DataFrame): DataFrame dengan hasil prediksi sentimen | |
| aspek_columns (list): List nama kolom aspek yang akan divisualisasikan | |
| """ | |
| # Validasi: cek apakah data dan kolom aspek tersedia | |
| if df_predicted.empty or not set(aspek_columns).issubset(df_predicted.columns): | |
| st.warning("Data atau kolom aspek tidak tersedia untuk ditampilkan.") | |
| return | |
| # Transform data dari wide format ke long format untuk visualisasi | |
| df_long = df_predicted.melt( | |
| value_vars=aspek_columns, | |
| var_name="aspek", | |
| value_name="sentimen" | |
| ) | |
| # Konversi sentimen ke categorical untuk sorting yang konsisten | |
| df_long["sentimen"] = pd.Categorical( | |
| df_long["sentimen"], | |
| categories=category_order, | |
| ordered=True | |
| ) | |
| # Hitung jumlah setiap kombinasi aspek-sentimen | |
| count_data = df_long.groupby( | |
| ["aspek", "sentimen"], observed=False | |
| ).size().reset_index(name="jumlah") | |
| # Buat bar chart dengan Plotly | |
| fig = px.bar( | |
| count_data, | |
| x="aspek", | |
| y="jumlah", | |
| color="sentimen", | |
| barmode="group", # Bar dikelompokkan berdampingan | |
| color_discrete_map=sentimen_palette, | |
| category_orders={"sentimen": category_order} | |
| ) | |
| fig.update_layout(title="Distribusi Sentimen per Aspek") | |
| # Tampilkan chart di Streamlit | |
| st.plotly_chart(fig, use_container_width=True, config=config_options) | |
| def show_sentiment_pie_chart(df_predicted, aspek_columns): | |
| """ | |
| Menampilkan pie chart distribusi total sentimen dari semua aspek. | |
| Chart menampilkan proporsi keseluruhan sentimen dalam bentuk donut chart. | |
| Args: | |
| df_predicted (pd.DataFrame): DataFrame dengan hasil prediksi sentimen | |
| aspek_columns (list): List nama kolom aspek | |
| """ | |
| # Flatten semua nilai sentimen dari semua aspek menjadi satu array | |
| sentimen_total = df_predicted[aspek_columns].values.ravel() | |
| # Hitung frekuensi setiap sentimen | |
| sentimen_counts = pd.Series(sentimen_total).value_counts().reset_index() | |
| sentimen_counts.columns = ["sentimen", "jumlah"] | |
| sentimen_counts = sentimen_counts.sort_values("jumlah", ascending=False) | |
| # Buat pie chart (donut chart dengan hole=0.3) | |
| fig = px.pie( | |
| sentimen_counts, | |
| names="sentimen", | |
| values="jumlah", | |
| color="sentimen", | |
| color_discrete_map=sentimen_palette, | |
| hole=0.3 # Buat donut chart | |
| ) | |
| fig.update_layout(title="Total Komposisi Sentimen") | |
| # Tampilkan persentase dan label di dalam chart | |
| fig.update_traces(textposition='inside', textinfo='percent+label') | |
| st.plotly_chart(fig, use_container_width=True, config=config_options) | |
| def show_year_distribution(df): | |
| """ | |
| Menampilkan distribusi jumlah kritik/saran per tahun. | |
| Jika kolom 'tahun' tidak ada, akan mencoba ekstrak dari kolom 'tanggal'. | |
| Args: | |
| df (pd.DataFrame): DataFrame input | |
| Returns: | |
| bool/None: True jika berhasil, None jika kolom tidak tersedia | |
| """ | |
| # Coba ekstrak tahun dari kolom tanggal jika kolom tahun tidak ada | |
| if 'tanggal' in df.columns and 'tahun' not in df.columns: | |
| df['tahun'] = pd.to_datetime(df['tanggal'], errors='coerce').dt.year | |
| # Validasi: return None jika tidak ada kolom tahun | |
| if 'tahun' not in df.columns: | |
| return None | |
| # Filter data yang memiliki nilai tahun valid | |
| df_tahun = df.dropna(subset=['tahun']).copy() | |
| if df_tahun.empty: | |
| return None | |
| # Konversi tahun ke integer | |
| df_tahun['tahun'] = df_tahun['tahun'].astype(int) | |
| # Hitung frekuensi per tahun | |
| year_counts = df_tahun['tahun'].value_counts().reset_index() | |
| year_counts.columns = ['tahun', 'jumlah'] | |
| year_counts = year_counts.sort_values('jumlah', ascending=False) | |
| # Buat bar chart | |
| fig = px.bar( | |
| year_counts, | |
| x='tahun', | |
| y='jumlah', | |
| color='tahun', | |
| title="Distribusi Kritik/Saran per Tahun" | |
| ) | |
| # Treat tahun sebagai kategori untuk menghindari interpolasi | |
| fig.update_layout(xaxis=dict(type='category')) | |
| st.plotly_chart(fig, use_container_width=True, config=config_options) | |
| return True | |
| def show_semester_distribution(df): | |
| """ | |
| Menampilkan distribusi jumlah kritik/saran per semester. | |
| Args: | |
| df (pd.DataFrame): DataFrame input | |
| Returns: | |
| bool/None: True jika berhasil, None jika kolom tidak tersedia | |
| """ | |
| # Validasi: cek apakah kolom semester ada | |
| if 'semester' not in df.columns: | |
| return None | |
| # Hitung frekuensi per semester | |
| semester_counts = df['semester'].value_counts().reset_index() | |
| semester_counts.columns = ['semester', 'jumlah'] | |
| semester_counts = semester_counts.sort_values('jumlah', ascending=False) | |
| # Buat bar chart | |
| fig = px.bar( | |
| semester_counts, | |
| x='semester', | |
| y='jumlah', | |
| color='semester', | |
| title="Distribusi Kritik/Saran per Semester" | |
| ) | |
| # Sort berdasarkan total descending | |
| fig.update_layout(xaxis=dict(categoryorder='total descending')) | |
| st.plotly_chart(fig, use_container_width=True, config=config_options) | |
| return True | |
| def show_prodi_distribution(df): | |
| """ | |
| Menampilkan jumlah kritik/saran per program studi dalam bentuk horizontal bar chart. | |
| Args: | |
| df (pd.DataFrame): DataFrame input | |
| Returns: | |
| bool/None: True jika berhasil, None jika kolom tidak tersedia | |
| """ | |
| # Validasi: cek apakah kolom nama_prodi ada | |
| if 'nama_prodi' not in df.columns: | |
| return None | |
| # Hitung frekuensi per program studi | |
| prodi_counts = df['nama_prodi'].value_counts().reset_index() | |
| prodi_counts.columns = ['nama_prodi', 'jumlah'] | |
| # Sort ascending untuk horizontal bar (terbanyak di atas) | |
| prodi_counts = prodi_counts.sort_values(by='jumlah', ascending=True) | |
| # Buat horizontal bar chart | |
| fig = px.bar( | |
| prodi_counts, | |
| x='jumlah', | |
| y='nama_prodi', | |
| orientation='h', # Horizontal orientation | |
| color='jumlah', | |
| title="Jumlah Kritik/Saran per Program Studi" | |
| ) | |
| st.plotly_chart(fig, use_container_width=True, config=config_options) | |
| return True | |
| def show_top10_matkul_distribution(df): | |
| """ | |
| Menampilkan 10 mata kuliah dengan jumlah kritik/saran terbanyak. | |
| Format: [kode_matakuliah] - [nama_matakuliah] | |
| Args: | |
| df (pd.DataFrame): DataFrame input | |
| Returns: | |
| bool/None: True jika berhasil, None jika kolom tidak tersedia | |
| """ | |
| # Validasi: cek apakah kolom yang diperlukan ada | |
| required_cols = ['nama_matakuliah', 'kode_matakuliah'] | |
| missing_cols = [col for col in required_cols if col not in df.columns] | |
| if missing_cols: | |
| return None | |
| # Group by kode dan nama mata kuliah, ambil 10 teratas | |
| matkul_counts = ( | |
| df.groupby(['kode_matakuliah', 'nama_matakuliah'], observed=False) | |
| .size() | |
| .reset_index(name='jumlah') | |
| .sort_values(by='jumlah', ascending=False) | |
| .head(10) | |
| ) | |
| # Buat label gabungan: "kode - nama" | |
| matkul_counts['label'] = ( | |
| matkul_counts['kode_matakuliah'] + " - " + | |
| matkul_counts['nama_matakuliah'] | |
| ) | |
| # Sort ascending untuk horizontal bar (terbanyak di atas) | |
| matkul_counts = matkul_counts.sort_values(by='jumlah', ascending=True) | |
| # Buat horizontal bar chart | |
| fig = px.bar( | |
| matkul_counts, | |
| x='jumlah', | |
| y='label', | |
| orientation='h', | |
| title="Top 10 Mata Kuliah Berdasarkan Kritik/Saran", | |
| color='jumlah' | |
| ) | |
| st.plotly_chart(fig, use_container_width=True, config=config_options) | |
| return True | |
| def show_sentiment_by_year(df, aspek_columns): | |
| """ | |
| Menampilkan distribusi sentimen per tahun dalam bentuk grouped bar chart. | |
| Menunjukkan bagaimana sentimen berubah dari tahun ke tahun. | |
| Args: | |
| df (pd.DataFrame): DataFrame input | |
| aspek_columns (list): List nama kolom aspek | |
| Returns: | |
| bool/None: True jika berhasil, None jika kolom tidak tersedia | |
| """ | |
| # Coba ekstrak tahun dari kolom tanggal jika kolom tahun tidak ada | |
| if 'tanggal' in df.columns and 'tahun' not in df.columns: | |
| df['tahun'] = pd.to_datetime(df['tanggal'], errors='coerce').dt.year | |
| # Validasi: return None jika tidak ada kolom tahun | |
| if 'tahun' not in df.columns: | |
| return None | |
| # Transform data dari wide ke long format, keep tahun sebagai ID variable | |
| df_long = df.melt( | |
| id_vars=['tahun'], | |
| value_vars=aspek_columns, | |
| var_name='aspek', | |
| value_name='sentimen' | |
| ) | |
| # Group by tahun dan sentimen, hitung frekuensi | |
| year_sentiment = df_long.groupby( | |
| ['tahun', 'sentimen'], observed=False | |
| ).size().reset_index(name='jumlah') | |
| year_sentiment = year_sentiment.sort_values('jumlah', ascending=False) | |
| # Buat grouped bar chart | |
| fig = px.bar( | |
| year_sentiment, | |
| x='tahun', | |
| y='jumlah', | |
| color='sentimen', | |
| barmode='group', # Bars dikelompokkan per tahun | |
| color_discrete_map=sentimen_palette | |
| ) | |
| fig.update_layout(title="Distribusi Sentimen Kritik/Saran per Tahun") | |
| st.plotly_chart(fig, use_container_width=True, config=config_options) | |
| return True | |
| def show_sentiment_by_semester(df, aspek_columns): | |
| """ | |
| Menampilkan distribusi sentimen per semester dalam bentuk grouped bar chart. | |
| Args: | |
| df (pd.DataFrame): DataFrame input | |
| aspek_columns (list): List nama kolom aspek | |
| Returns: | |
| bool/None: True jika berhasil, None jika kolom tidak tersedia | |
| """ | |
| # Validasi: cek apakah kolom semester ada | |
| if 'semester' not in df.columns: | |
| return None | |
| # Transform data dari wide ke long format, keep semester sebagai ID variable | |
| df_long = df.melt( | |
| id_vars=['semester'], | |
| value_vars=aspek_columns, | |
| var_name='aspek', | |
| value_name='sentimen' | |
| ) | |
| # Group by semester dan sentimen, hitung frekuensi | |
| semester_sentiment = df_long.groupby( | |
| ['semester', 'sentimen'], observed=False | |
| ).size().reset_index(name='jumlah') | |
| semester_sentiment = semester_sentiment.sort_values( | |
| 'jumlah', ascending=False) | |
| # Buat grouped bar chart | |
| fig = px.bar( | |
| semester_sentiment, | |
| x='semester', | |
| y='jumlah', | |
| color='sentimen', | |
| barmode='group', # Bars dikelompokkan per semester | |
| color_discrete_map=sentimen_palette | |
| ) | |
| fig.update_layout(title="Distribusi Sentimen Kritik/Saran per Semester") | |
| st.plotly_chart(fig, use_container_width=True, config=config_options) | |
| return True | |
| def show_sentiment_by_prodi(df, aspek_columns): | |
| """ | |
| Menampilkan distribusi sentimen per program studi dalam horizontal grouped bar chart. | |
| Program studi diurutkan berdasarkan total jumlah kritik/saran. | |
| Args: | |
| df (pd.DataFrame): DataFrame input | |
| aspek_columns (list): List nama kolom aspek | |
| Returns: | |
| bool/None: True jika berhasil, None jika kolom tidak tersedia | |
| """ | |
| # Validasi: cek apakah kolom nama_prodi ada | |
| if 'nama_prodi' not in df.columns: | |
| return None | |
| # Transform data dari wide ke long format | |
| df_long = df.melt( | |
| id_vars=['nama_prodi'], | |
| value_vars=aspek_columns, | |
| var_name='aspek', | |
| value_name='sentimen' | |
| ) | |
| # Group by prodi dan sentimen, hitung frekuensi | |
| prodi_sentiment = ( | |
| df_long.groupby(['nama_prodi', 'sentimen'], observed=False) | |
| .size() | |
| .reset_index(name='jumlah') | |
| ) | |
| # Hitung total per prodi untuk sorting | |
| total_per_prodi = ( | |
| prodi_sentiment.groupby('nama_prodi')['jumlah'] | |
| .sum() | |
| .sort_values(ascending=False) | |
| ) | |
| # Reverse order untuk horizontal bar (terbanyak di atas) | |
| ordered_categories = total_per_prodi.index.tolist()[::-1] | |
| # Konversi ke categorical untuk maintain order | |
| prodi_sentiment['nama_prodi'] = pd.Categorical( | |
| prodi_sentiment['nama_prodi'], | |
| categories=ordered_categories, | |
| ordered=True | |
| ) | |
| # Buat horizontal grouped bar chart | |
| fig = px.bar( | |
| prodi_sentiment, | |
| y='nama_prodi', | |
| x='jumlah', | |
| color='sentimen', | |
| barmode='group', | |
| orientation='h', # Horizontal orientation | |
| color_discrete_map=sentimen_palette | |
| ) | |
| fig.update_layout( | |
| title="Distribusi Sentimen per Program Studi", | |
| yaxis={ | |
| 'categoryorder': 'array', | |
| 'categoryarray': ordered_categories | |
| } | |
| ) | |
| st.plotly_chart(fig, use_container_width=True, config=config_options) | |
| return True | |
| def show_sentiment_by_top10_matkul(df, aspek_columns): | |
| """ | |
| Menampilkan distribusi sentimen pada 10 mata kuliah dengan kritik/saran terbanyak. | |
| Chart menggunakan horizontal grouped bar, diurutkan berdasarkan total kritik/saran. | |
| Args: | |
| df (pd.DataFrame): DataFrame input | |
| aspek_columns (list): List nama kolom aspek | |
| Returns: | |
| bool/None: True jika berhasil, None jika kolom tidak tersedia | |
| """ | |
| # Validasi: cek apakah kolom yang diperlukan ada | |
| required_cols = ['kode_matakuliah', 'nama_matakuliah'] | |
| missing_cols = [col for col in required_cols if col not in df.columns] | |
| if missing_cols: | |
| return None | |
| # Identifikasi top 10 mata kuliah berdasarkan jumlah kritik/saran | |
| df_top10 = ( | |
| df.groupby(['kode_matakuliah', 'nama_matakuliah'], observed=False) | |
| .size() | |
| .sort_values(ascending=False) | |
| .head(10) | |
| .index | |
| ) | |
| # Filter data hanya untuk top 10 mata kuliah | |
| df_filtered = df[df.set_index( | |
| ['kode_matakuliah', 'nama_matakuliah']).index.isin(df_top10)] | |
| # Transform data dari wide ke long format | |
| df_long = df_filtered.melt( | |
| id_vars=['kode_matakuliah', 'nama_matakuliah'], | |
| value_vars=aspek_columns, | |
| var_name='aspek', | |
| value_name='sentimen' | |
| ) | |
| # Buat label gabungan: "kode - nama" | |
| df_long['label'] = ( | |
| df_long['kode_matakuliah'] + " - " + df_long['nama_matakuliah'] | |
| ) | |
| # Group by label dan sentimen, hitung frekuensi | |
| matkul_sentiment = ( | |
| df_long.groupby(['label', 'sentimen'], observed=False) | |
| .size() | |
| .reset_index(name='jumlah') | |
| ) | |
| # Hitung total per label untuk sorting | |
| total_per_label = ( | |
| matkul_sentiment.groupby('label')['jumlah'] | |
| .sum() | |
| .sort_values(ascending=False) | |
| ) | |
| # Reverse order untuk horizontal bar (terbanyak di atas) | |
| ordered_labels = total_per_label.index.tolist()[::-1] | |
| # Konversi ke categorical untuk maintain order | |
| matkul_sentiment['label'] = pd.Categorical( | |
| matkul_sentiment['label'], | |
| categories=ordered_labels, | |
| ordered=True | |
| ) | |
| # Buat horizontal grouped bar chart | |
| fig = px.bar( | |
| matkul_sentiment, | |
| y='label', | |
| x='jumlah', | |
| color='sentimen', | |
| barmode='group', | |
| orientation='h', | |
| color_discrete_map=sentimen_palette | |
| ) | |
| fig.update_layout( | |
| title="Distribusi Sentimen pada Top 10 Mata Kuliah", | |
| yaxis={ | |
| 'categoryorder': 'array', | |
| 'categoryarray': ordered_labels | |
| } | |
| ) | |
| st.plotly_chart(fig, use_container_width=True, config=config_options) | |
| return True | |