absa-indobert-web / visualization.py
zdannn2808's picture
merapikan file, menambahkan komentar penting pada config.py, preprocessing.py, visualization.py, dan app.py
9e73d6d verified
raw
history blame
16.6 kB
"""
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