absa-indobert-web / visualization.py
zdannn2808's picture
add some comments
aff4068 verified
"""
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
# Definisi warna untuk setiap kategori sentimen
sentimen_palette = {
"netral": "#FFE24C",
"positif": "#4CFF72",
"negatif": "#FF4C4C"
}
# Urutan kategori untuk konsistensi tampilan di semua chart
category_order = ["netral", "positif", "negatif"]
# Konfigurasi Plotly
config_options = {
"scrollZoom": False,
"displayModeBar": False
}
def show_sentiment_bar_chart(df_predicted, aspek_columns):
"""Menampilkan bar chart distribusi sentimen per aspek."""
# Validasi data dan kolom yang diperlukan
if df_predicted.empty or not set(aspek_columns).issubset(df_predicted.columns):
st.warning("Data atau kolom aspek tidak tersedia untuk ditampilkan.")
return
# Transformasi dari wide ke long format untuk visualisasi
df_long = df_predicted.melt(
value_vars=aspek_columns,
var_name="aspek",
value_name="sentimen"
)
# Konversi ke categorical untuk memastikan urutan yang konsisten
df_long["sentimen"] = pd.Categorical(
df_long["sentimen"],
categories=category_order,
ordered=True
)
# Agregasi data untuk menghitung jumlah per aspek dan sentimen
count_data = df_long.groupby(
["aspek", "sentimen"], observed=False
).size().reset_index(name="jumlah")
fig = px.bar(
count_data,
x="aspek",
y="jumlah",
color="sentimen",
barmode="group",
color_discrete_map=sentimen_palette,
category_orders={"sentimen": category_order}
)
fig.update_layout(title="Distribusi Sentimen per Aspek")
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."""
# Flatten semua nilai sentimen dari semua aspek menjadi 1D array
sentimen_total = df_predicted[aspek_columns].values.ravel()
sentimen_counts = pd.Series(sentimen_total).value_counts().reset_index()
sentimen_counts.columns = ["sentimen", "jumlah"]
sentimen_counts = sentimen_counts.sort_values("jumlah", ascending=False)
# Donut chart dengan hole parameter
fig = px.pie(sentimen_counts, names="sentimen", values="jumlah",
color="sentimen", color_discrete_map=sentimen_palette,
hole=0.3)
fig.update_layout(title="Total Komposisi Sentimen")
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."""
# Ekstraksi tahun dari kolom tanggal jika kolom tahun tidak tersedia
if 'tanggal' in df.columns and 'tahun' not in df.columns:
df['tahun'] = pd.to_datetime(df['tanggal'], errors='coerce').dt.year
# Return None jika tidak ada data tahun (untuk handling di pemanggil)
if 'tahun' not in df.columns:
return None
df_tahun = df.dropna(subset=['tahun']).copy()
if df_tahun.empty:
return None
df_tahun['tahun'] = df_tahun['tahun'].astype(int)
year_counts = df_tahun['tahun'].value_counts().reset_index()
year_counts.columns = ['tahun', 'jumlah']
year_counts = year_counts.sort_values('jumlah', ascending=False)
fig = px.bar(year_counts, x='tahun', y='jumlah',
color='tahun', title="Distribusi Kritik/Saran per Tahun")
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."""
if 'semester' not in df.columns:
return None
semester_counts = df['semester'].value_counts().reset_index()
semester_counts.columns = ['semester', 'jumlah']
semester_counts = semester_counts.sort_values('jumlah', ascending=False)
fig = px.bar(semester_counts, x='semester', y='jumlah',
color='semester', title="Distribusi Kritik/Saran per Semester")
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."""
if 'nama_prodi' not in df.columns:
return None
prodi_counts = df['nama_prodi'].value_counts().reset_index()
prodi_counts.columns = ['nama_prodi', 'jumlah']
# Sort ascending untuk horizontal bar (nilai kecil di bawah)
prodi_counts = prodi_counts.sort_values(by='jumlah', ascending=True)
fig = px.bar(
prodi_counts,
x='jumlah',
y='nama_prodi',
orientation='h',
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."""
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
# Groupby untuk menghitung frekuensi per mata kuliah
matkul_counts = (
df.groupby(['kode_matakuliah', 'nama_matakuliah'], observed=False)
.size()
.reset_index(name='jumlah')
.sort_values(by='jumlah', ascending=False)
.head(10)
)
# Gabungkan kode dan nama untuk label yang informatif
matkul_counts['label'] = (
matkul_counts['kode_matakuliah'] + " - " +
matkul_counts['nama_matakuliah']
)
matkul_counts = matkul_counts.sort_values(by='jumlah', ascending=True)
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."""
# Ekstraksi tahun dari kolom tanggal jika diperlukan
if 'tanggal' in df.columns and 'tahun' not in df.columns:
df['tahun'] = pd.to_datetime(df['tanggal'], errors='coerce').dt.year
if 'tahun' not in df.columns:
return None
# Transformasi ke long format dengan id_vars tahun
df_long = df.melt(id_vars=['tahun'],
value_vars=aspek_columns,
var_name='aspek',
value_name='sentimen')
year_sentiment = df_long.groupby(
['tahun', 'sentimen'], observed=False
).size().reset_index(name='jumlah')
year_sentiment = year_sentiment.sort_values('jumlah', ascending=False)
fig = px.bar(year_sentiment, x='tahun', y='jumlah', color='sentimen',
barmode='group', 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."""
if 'semester' not in df.columns:
return None
df_long = df.melt(id_vars=['semester'],
value_vars=aspek_columns,
var_name='aspek',
value_name='sentimen')
semester_sentiment = df_long.groupby(
['semester', 'sentimen'], observed=False
).size().reset_index(name='jumlah')
semester_sentiment = semester_sentiment.sort_values(
'jumlah', ascending=False)
fig = px.bar(semester_sentiment, x='semester', y='jumlah', color='sentimen',
barmode='group', 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."""
if 'nama_prodi' not in df.columns:
return None
df_long = df.melt(
id_vars=['nama_prodi'],
value_vars=aspek_columns,
var_name='aspek',
value_name='sentimen'
)
prodi_sentiment = (
df_long.groupby(['nama_prodi', 'sentimen'], observed=False)
.size()
.reset_index(name='jumlah')
)
# Hitung total per prodi untuk mengurutkan dari terbanyak ke sedikit
total_per_prodi = (
prodi_sentiment.groupby('nama_prodi')['jumlah']
.sum()
.sort_values(ascending=False)
)
# Reverse order untuk horizontal bar (nilai besar di atas)
ordered_categories = total_per_prodi.index.tolist()[::-1]
# Konversi ke categorical untuk kontrol urutan tampilan
prodi_sentiment['nama_prodi'] = pd.Categorical(
prodi_sentiment['nama_prodi'],
categories=ordered_categories,
ordered=True
)
fig = px.bar(
prodi_sentiment,
y='nama_prodi',
x='jumlah',
color='sentimen',
barmode='group',
orientation='h',
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 teratas."""
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
# Filter top 10 mata kuliah berdasarkan frekuensi
df_top10 = (
df.groupby(['kode_matakuliah', 'nama_matakuliah'], observed=False)
.size()
.sort_values(ascending=False)
.head(10)
.index
)
df_filtered = df[df.set_index(
['kode_matakuliah', 'nama_matakuliah']).index.isin(df_top10)]
df_long = df_filtered.melt(
id_vars=['kode_matakuliah', 'nama_matakuliah'],
value_vars=aspek_columns,
var_name='aspek',
value_name='sentimen'
)
# Gabungkan kode dan nama untuk label
df_long['label'] = (
df_long['kode_matakuliah'] + " - " + df_long['nama_matakuliah']
)
matkul_sentiment = (
df_long.groupby(['label', 'sentimen'], observed=False)
.size()
.reset_index(name='jumlah')
)
# Urutkan berdasarkan total sentimen per mata kuliah
total_per_label = (
matkul_sentiment.groupby('label')['jumlah']
.sum()
.sort_values(ascending=False)
)
ordered_labels = total_per_label.index.tolist()[::-1]
matkul_sentiment['label'] = pd.Categorical(
matkul_sentiment['label'],
categories=ordered_labels,
ordered=True
)
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