FINANCIAL DISTRESS PREDICTION

TOGGLE GColab / Kaggle

platform = "gcolab"
# platform = "kaggle"
compute_type = "cpu"  # 'cpu' atau 'gpu'
import os

if platform == "gcolab":
    # ----- Google Colab ----- #

    import importlib.util
    if importlib.util.find_spec("catboost") is None:
        %pip install catboost -q
    if importlib.util.find_spec("optuna") is None:
        %pip install optuna -q

    from google.colab import drive, output

    MOUNT_POINT = "/content/drive"
    prefix_path = f"{MOUNT_POINT}/MyDrive/SKRIPSI/Dataset/"
    save_path = prefix_path

    if not os.path.isdir(f"{MOUNT_POINT}/MyDrive"):
        drive.mount(MOUNT_POINT)

    if not os.path.isdir(prefix_path):
        raise FileNotFoundError(f"Folder tidak ditemukan: {prefix_path}")
    
    if not os.path.isdir(save_path):
        raise FileNotFoundError(f"Folder tidak ditemukan: {save_path}")

elif platform == "kaggle":
    # ----- Kaggle ----- #
    
    prefix_path = "/kaggle/input/idx-financial-data/"
    save_path = "/kaggle/working/"
    os.makedirs(save_path + 'pickle', exist_ok=True)

    if compute_type == 'cpu':
        prefix_path = "/kaggle/input/datasets/daffarestupratama/idx-financial-data/"

else:

    raise ValueError("Invalid platform choice")

Libraries

Install

%pip install -U kaleido==0.2.1 -q

Import

import warnings
import pandas as pd
import numpy as np

import sys

import re
from IPython.display import clear_output, display

import math
import textwrap

import pickle
import matplotlib.pyplot as plt
from matplotlib.colors import Normalize, LinearSegmentedColormap
from matplotlib.collections import LineCollection
from matplotlib.patches import Patch
from matplotlib.lines import Line2D

import plotly.graph_objects as go

import seaborn as sns
from statsmodels.graphics.mosaicplot import mosaic
from scipy.stats import chi2_contingency
from scipy.stats import fisher_exact
from sklearn.experimental import enable_iterative_imputer

from sklearn.model_selection import train_test_split, GridSearchCV, cross_val_predict
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import AdaBoostClassifier
from sklearn.preprocessing import StandardScaler, MinMaxScaler, OrdinalEncoder
from sklearn.decomposition import PCA
from sklearn.cluster import KMeans
from sklearn.model_selection import train_test_split, RandomizedSearchCV, StratifiedKFold, GridSearchCV
from sklearn.base import BaseEstimator, ClassifierMixin, clone, TransformerMixin
from sklearn.utils.validation import check_is_fitted
from sklearn.impute import KNNImputer, SimpleImputer, IterativeImputer
from sklearn.metrics import roc_auc_score, average_precision_score, classification_report, confusion_matrix, precision_recall_curve, accuracy_score, precision_score, recall_score, f1_score, classification_report, roc_curve, matthews_corrcoef
from sklearn.pipeline import Pipeline
from sklearn.feature_selection import mutual_info_classif
from imblearn.pipeline import Pipeline as ImbPipeline
from imblearn.over_sampling import SMOTE, RandomOverSampler

import optuna
import statsmodels.api as sm
from statsmodels.stats.outliers_influence import variance_inflation_factor
from statsmodels.tools.tools import add_constant
from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier
from xgboost import XGBClassifier
from lightgbm import LGBMClassifier
from catboost import CatBoostClassifier
import shap

Set Options & Constants

pd.set_option('display.max_columns', None)
pd.set_option('display.float_format', lambda x: '%.6f' % x)
RANDOM_STATE = 42

Helper Functions

# Untuk ubah df ndarray jadi numpy dan contiguous
# Dipakai saat pindah dari gpu ke cpu
def to_float32_contiguous(X):
    # X bisa DataFrame atau ndarray
    arr = X.to_numpy() if hasattr(X, "to_numpy") else np.asarray(X)
    return np.asarray(arr, dtype=np.float32, order="C")
from IPython.display import display, Audio

# Membuat custom exception agar error log tidak terlalu panjang dan berantakan
class StopExecution(Exception):
    def _render_traceback_(self):
        pass

def limit():
    # Menggunakan modul standar IPython untuk memutar audio
    for _ in range(2):
        display(Audio(url="https://upload.wikimedia.org/wikipedia/commons/0/05/Beep-09.ogg", autoplay=True))
    
    print("Exiting program to limit code run.")
    
    # Menghentikan eksekusi cell selanjutnya tanpa mematikan kernel
    raise StopExecution("Eksekusi dihentikan secara sengaja.")

def end():
    for _ in range(5):
        display(Audio(url="https://upload.wikimedia.org/wikipedia/commons/0/05/Beep-09.ogg", autoplay=True))
    
    print("End of program.")
    raise StopExecution("Program selesai.")

Business Understanding

Tujuan Bisnis

Kriteria Sukses Bisnis

Menerjemahkan ke Tujuan Data Mining

Daftar Perusahaan

Load Daftar Nama dan Kode Saham

df_overview = pd.read_excel(prefix_path + 'Daftar_Saham.xlsx')
df_overview
No Kode Nama Perusahaan Tanggal Pencatatan Saham Papan Pencatatan
0 1 AALI Astra Agro Lestari Tbk. 09 Des 1997 1.924.688.333 Utama
1 2 ABBA Mahaka Media Tbk. 03 Apr 2002 3.935.892.857 Pemantauan Khusus
2 3 ABDA Asuransi Bina Dana Arta Tbk. 06 Jul 1989 620.806.680 Pemantauan Khusus
3 4 ABMM ABM Investama Tbk. 06 Des 2011 2.753.165.000 Utama
4 5 ACES Aspirasi Hidup Indonesia Tbk. 06 Nov 2007 17.120.389.700 Utama
... ... ... ... ... ... ...
950 951 ASPI Andalan Sakti Primaindo Tbk. 17 Feb 2020 681.823.317 Pemantauan Khusus
951 952 ESTA Esta Multi Usaha Tbk. 09 Mar 2020 2.425.354.179 Pengembangan
952 953 BESS Batulicin Nusantara Maritim Tb 09 Mar 2020 3.440.455.528 Pengembangan
953 954 AMAN Makmur Berkah Amanda Tbk. 13 Mar 2020 3.873.500.000 Pengembangan
954 955 CARE Metro Healthcare Indonesia Tbk 13 Mar 2020 33.250.000.000 Pengembangan

955 rows × 6 columns

Load Papan Pemantauan Khusus

df_ppk = pd.read_excel(prefix_path + 'Data_PPK.xlsx')
df_ppk
Kode Saham Nama Perusahaan Tanggal Masuk Tanggal Keluar Kriteria
0 OCAP ONIX CAPITAL Tbk 19 Jul 2021 NaN 2, 3, 5, 7
1 PICO Pelangi Indah Canindo Tbk 19 Jul 2021 13 Jun 2022 8
2 BINA PT Bank Ina Perdana Tbk. 21 Jul 2021 20 Agt 2021 10
3 GMFI PT Garuda Maintenance Facility Aero Asia Tbk. 29 Jul 2021 NaN 5
4 BOLA PT Bali Bintang Sejahtera Tbk. 20 Agt 2021 20 Sep 2021 10
... ... ... ... ... ...
401 PGJO PT Tourindo Guide Indonesia Tbk. 17 Sep 2025 NaN 10
402 IRSX PT Aviana Sinar Abadi Tbk 18 Sep 2025 NaN 1
403 RONY PT Aracord Nusantara Group Tbk 18 Sep 2025 NaN 7, 10
404 LAPD Leyand International Tbk 19 Sep 2025 NaN 1
405 PIPA PT Multi Makmur Lemindo Tbk. 22 Sep 2025 NaN 10

406 rows × 5 columns

Merge Data Daftar Saham

df_emiten_idx = pd.merge(df_overview, df_ppk, left_on='Kode', right_on='Kode Saham', how='left')
df_emiten_idx
No Kode Nama Perusahaan_x Tanggal Pencatatan Saham Papan Pencatatan Kode Saham Nama Perusahaan_y Tanggal Masuk Tanggal Keluar Kriteria
0 1 AALI Astra Agro Lestari Tbk. 09 Des 1997 1.924.688.333 Utama NaN NaN NaN NaN NaN
1 2 ABBA Mahaka Media Tbk. 03 Apr 2002 3.935.892.857 Pemantauan Khusus ABBA Mahaka Media Tbk 31 Mei 2024 NaN 1, 5
2 3 ABDA Asuransi Bina Dana Arta Tbk. 06 Jul 1989 620.806.680 Pemantauan Khusus ABDA Asuransi Bina Dana Arta Tbk 09 Apr 2025 NaN 7
3 4 ABMM ABM Investama Tbk. 06 Des 2011 2.753.165.000 Utama NaN NaN NaN NaN NaN
4 5 ACES Aspirasi Hidup Indonesia Tbk. 06 Nov 2007 17.120.389.700 Utama NaN NaN NaN NaN NaN
... ... ... ... ... ... ... ... ... ... ... ...
950 951 ASPI Andalan Sakti Primaindo Tbk. 17 Feb 2020 681.823.317 Pemantauan Khusus ASPI PT Andalan Sakti Primaindo Tbk. 24 Jul 2025 04 Agt 2025 10
951 952 ESTA Esta Multi Usaha Tbk. 09 Mar 2020 2.425.354.179 Pengembangan ESTA PT Esta Multi Usaha Tbk. 30 Apr 2025 04 Jun 2025 6
952 953 BESS Batulicin Nusantara Maritim Tb 09 Mar 2020 3.440.455.528 Pengembangan BESS PT Batulicin Nusantara Maritim Tbk. 10 Mar 2025 19 Mar 2025 10
953 954 AMAN Makmur Berkah Amanda Tbk. 13 Mar 2020 3.873.500.000 Pengembangan NaN NaN NaN NaN NaN
954 955 CARE Metro Healthcare Indonesia Tbk 13 Mar 2020 33.250.000.000 Pengembangan NaN NaN NaN NaN NaN

955 rows × 11 columns

Hapus kolom yang redundant

# Drop redundant columns
df_emiten_idx = df_emiten_idx.drop(columns=['Kode Saham', 'Nama Perusahaan_y', 'No'])

# Rename 'Nama Perusahaan_x' column
df_emiten_idx = df_emiten_idx.rename(columns={'Nama Perusahaan_x': 'Nama Perusahaan'})

# Display the updated DataFrame
df_emiten_idx
Kode Nama Perusahaan Tanggal Pencatatan Saham Papan Pencatatan Tanggal Masuk Tanggal Keluar Kriteria
0 AALI Astra Agro Lestari Tbk. 09 Des 1997 1.924.688.333 Utama NaN NaN NaN
1 ABBA Mahaka Media Tbk. 03 Apr 2002 3.935.892.857 Pemantauan Khusus 31 Mei 2024 NaN 1, 5
2 ABDA Asuransi Bina Dana Arta Tbk. 06 Jul 1989 620.806.680 Pemantauan Khusus 09 Apr 2025 NaN 7
3 ABMM ABM Investama Tbk. 06 Des 2011 2.753.165.000 Utama NaN NaN NaN
4 ACES Aspirasi Hidup Indonesia Tbk. 06 Nov 2007 17.120.389.700 Utama NaN NaN NaN
... ... ... ... ... ... ... ... ...
950 ASPI Andalan Sakti Primaindo Tbk. 17 Feb 2020 681.823.317 Pemantauan Khusus 24 Jul 2025 04 Agt 2025 10
951 ESTA Esta Multi Usaha Tbk. 09 Mar 2020 2.425.354.179 Pengembangan 30 Apr 2025 04 Jun 2025 6
952 BESS Batulicin Nusantara Maritim Tb 09 Mar 2020 3.440.455.528 Pengembangan 10 Mar 2025 19 Mar 2025 10
953 AMAN Makmur Berkah Amanda Tbk. 13 Mar 2020 3.873.500.000 Pengembangan NaN NaN NaN
954 CARE Metro Healthcare Indonesia Tbk 13 Mar 2020 33.250.000.000 Pengembangan NaN NaN NaN

955 rows × 8 columns

Convert Tanggal

# Map Indonesian month abbreviations to English ones
indo_to_eng_month = {
    'Jan': 'Jan', 'Feb': 'Feb', 'Mar': 'Mar', 'Apr': 'Apr', 'Mei': 'May', 'Jun': 'Jun',
    'Jul': 'Jul', 'Agt': 'Aug', 'Sep': 'Sep', 'Okt': 'Oct', 'Nov': 'Nov', 'Des': 'Dec'
}

# Convert date columns to datetime objects
date_cols = ['Tanggal Pencatatan', 'Tanggal Masuk', 'Tanggal Keluar']
for col in date_cols:
    # Replace Indonesian month abbreviations with English ones
    df_emiten_idx[col] = df_emiten_idx[col].astype(str).replace(indo_to_eng_month, regex=True)
    df_emiten_idx[col] = pd.to_datetime(df_emiten_idx[col], format='%d %b %Y', errors='coerce')

df_emiten_idx
Kode Nama Perusahaan Tanggal Pencatatan Saham Papan Pencatatan Tanggal Masuk Tanggal Keluar Kriteria
0 AALI Astra Agro Lestari Tbk. 1997-12-09 1.924.688.333 Utama NaT NaT NaN
1 ABBA Mahaka Media Tbk. 2002-04-03 3.935.892.857 Pemantauan Khusus 2024-05-31 NaT 1, 5
2 ABDA Asuransi Bina Dana Arta Tbk. 1989-07-06 620.806.680 Pemantauan Khusus 2025-04-09 NaT 7
3 ABMM ABM Investama Tbk. 2011-12-06 2.753.165.000 Utama NaT NaT NaN
4 ACES Aspirasi Hidup Indonesia Tbk. 2007-11-06 17.120.389.700 Utama NaT NaT NaN
... ... ... ... ... ... ... ... ...
950 ASPI Andalan Sakti Primaindo Tbk. 2020-02-17 681.823.317 Pemantauan Khusus 2025-07-24 2025-08-04 10
951 ESTA Esta Multi Usaha Tbk. 2020-03-09 2.425.354.179 Pengembangan 2025-04-30 2025-06-04 6
952 BESS Batulicin Nusantara Maritim Tb 2020-03-09 3.440.455.528 Pengembangan 2025-03-10 2025-03-19 10
953 AMAN Makmur Berkah Amanda Tbk. 2020-03-13 3.873.500.000 Pengembangan NaT NaT NaN
954 CARE Metro Healthcare Indonesia Tbk 2020-03-13 33.250.000.000 Pengembangan NaT NaT NaN

955 rows × 8 columns

Tambah Kolom PPK (Yes/No)

# Kolom Identifikasi PPK
df_emiten_idx['PPK'] = df_emiten_idx['Tanggal Masuk'].apply(lambda x: 'Yes' if pd.notna(x) else 'No')

df_emiten_idx
Kode Nama Perusahaan Tanggal Pencatatan Saham Papan Pencatatan Tanggal Masuk Tanggal Keluar Kriteria PPK
0 AALI Astra Agro Lestari Tbk. 1997-12-09 1.924.688.333 Utama NaT NaT NaN No
1 ABBA Mahaka Media Tbk. 2002-04-03 3.935.892.857 Pemantauan Khusus 2024-05-31 NaT 1, 5 Yes
2 ABDA Asuransi Bina Dana Arta Tbk. 1989-07-06 620.806.680 Pemantauan Khusus 2025-04-09 NaT 7 Yes
3 ABMM ABM Investama Tbk. 2011-12-06 2.753.165.000 Utama NaT NaT NaN No
4 ACES Aspirasi Hidup Indonesia Tbk. 2007-11-06 17.120.389.700 Utama NaT NaT NaN No
... ... ... ... ... ... ... ... ... ...
950 ASPI Andalan Sakti Primaindo Tbk. 2020-02-17 681.823.317 Pemantauan Khusus 2025-07-24 2025-08-04 10 Yes
951 ESTA Esta Multi Usaha Tbk. 2020-03-09 2.425.354.179 Pengembangan 2025-04-30 2025-06-04 6 Yes
952 BESS Batulicin Nusantara Maritim Tb 2020-03-09 3.440.455.528 Pengembangan 2025-03-10 2025-03-19 10 Yes
953 AMAN Makmur Berkah Amanda Tbk. 2020-03-13 3.873.500.000 Pengembangan NaT NaT NaN No
954 CARE Metro Healthcare Indonesia Tbk 2020-03-13 33.250.000.000 Pengembangan NaT NaT NaN No

955 rows × 9 columns

Kriteria Distress PPK dan Tambah Kolom Distress (Yes/No)

# Define the criteria values for distress
distress_criteria = ['2', '3', '5', '8', '9'] + ['1', '7'] + ['4', '6', '10', '11']

# Function to determine 'Distress' status
def is_distress(row):
    if row['PPK'] == 'Yes':
        kriteria_values = str(row['Kriteria']).split(', ')
        if any(item in distress_criteria for item in kriteria_values):
            return 'Yes'
    return 'No'

# Apply the function to create the 'Distress' column
df_emiten_idx['Distress_PPK'] = df_emiten_idx.apply(is_distress, axis=1)

# Display the updated DataFrame
df_emiten_idx
Kode Nama Perusahaan Tanggal Pencatatan Saham Papan Pencatatan Tanggal Masuk Tanggal Keluar Kriteria PPK Distress_PPK
0 AALI Astra Agro Lestari Tbk. 1997-12-09 1.924.688.333 Utama NaT NaT NaN No No
1 ABBA Mahaka Media Tbk. 2002-04-03 3.935.892.857 Pemantauan Khusus 2024-05-31 NaT 1, 5 Yes Yes
2 ABDA Asuransi Bina Dana Arta Tbk. 1989-07-06 620.806.680 Pemantauan Khusus 2025-04-09 NaT 7 Yes Yes
3 ABMM ABM Investama Tbk. 2011-12-06 2.753.165.000 Utama NaT NaT NaN No No
4 ACES Aspirasi Hidup Indonesia Tbk. 2007-11-06 17.120.389.700 Utama NaT NaT NaN No No
... ... ... ... ... ... ... ... ... ... ...
950 ASPI Andalan Sakti Primaindo Tbk. 2020-02-17 681.823.317 Pemantauan Khusus 2025-07-24 2025-08-04 10 Yes Yes
951 ESTA Esta Multi Usaha Tbk. 2020-03-09 2.425.354.179 Pengembangan 2025-04-30 2025-06-04 6 Yes Yes
952 BESS Batulicin Nusantara Maritim Tb 2020-03-09 3.440.455.528 Pengembangan 2025-03-10 2025-03-19 10 Yes Yes
953 AMAN Makmur Berkah Amanda Tbk. 2020-03-13 3.873.500.000 Pengembangan NaT NaT NaN No No
954 CARE Metro Healthcare Indonesia Tbk 2020-03-13 33.250.000.000 Pengembangan NaT NaT NaN No No

955 rows × 10 columns

Convert String Kriteria Jadi List

df_emiten_idx["Kriteria"] = (
    df_emiten_idx["Kriteria"]
      .astype("string")
      .str.strip()
      .str.split(r"\s*,\s*", regex=True)
)

df_emiten_idx["Kriteria"] = df_emiten_idx["Kriteria"].apply(
    lambda x: [int(i) for i in x if str(i).strip() != ""] if isinstance(x, list) else pd.NA
)
df_emiten_idx["Kriteria"].head()
Kriteria
0 <NA>
1 [1, 5]
2 [7]
3 <NA>
4 <NA>

df_emiten_idx["Kriteria"].iloc[1][1]
5
type(df_emiten_idx["Kriteria"].iloc[1][1])
int

Cek Null & Nol

# Check null
null_counts_emiten = df_emiten_idx.isnull().sum()

print("Jumlah nilai null per kolom di df_emiten:")
print(null_counts_emiten)

# Check for zero val
numerical_cols_emiten = df_emiten_idx.select_dtypes(include=['number']).columns
zero_counts_emiten = (df_emiten_idx[numerical_cols_emiten] == 0).sum()

print("\nJumlah nilai 0 per kolom numerik di df_emiten:")
print(zero_counts_emiten)
Jumlah nilai null per kolom di df_emiten:
Kode                    0
Nama Perusahaan         0
Tanggal Pencatatan      0
Saham                   0
Papan Pencatatan        0
Tanggal Masuk         549
Tanggal Keluar        743
Kriteria              549
PPK                     0
Distress_PPK            0
dtype: int64

Jumlah nilai 0 per kolom numerik di df_emiten:
Series([], dtype: float64)
# Print the result
print(f"Jumlah emiten yang distress berdasarkan PPK Kriteria tertentu:\n{len(df_emiten_idx[df_emiten_idx['Distress_PPK'] == 'Yes'])}")
Jumlah emiten yang distress berdasarkan PPK Kriteria tertentu:
406

Hasil Dataset Daftar Emiten IDX

df_emiten_idx
Kode Nama Perusahaan Tanggal Pencatatan Saham Papan Pencatatan Tanggal Masuk Tanggal Keluar Kriteria PPK Distress_PPK
0 AALI Astra Agro Lestari Tbk. 1997-12-09 1.924.688.333 Utama NaT NaT <NA> No No
1 ABBA Mahaka Media Tbk. 2002-04-03 3.935.892.857 Pemantauan Khusus 2024-05-31 NaT [1, 5] Yes Yes
2 ABDA Asuransi Bina Dana Arta Tbk. 1989-07-06 620.806.680 Pemantauan Khusus 2025-04-09 NaT [7] Yes Yes
3 ABMM ABM Investama Tbk. 2011-12-06 2.753.165.000 Utama NaT NaT <NA> No No
4 ACES Aspirasi Hidup Indonesia Tbk. 2007-11-06 17.120.389.700 Utama NaT NaT <NA> No No
... ... ... ... ... ... ... ... ... ... ...
950 ASPI Andalan Sakti Primaindo Tbk. 2020-02-17 681.823.317 Pemantauan Khusus 2025-07-24 2025-08-04 [10] Yes Yes
951 ESTA Esta Multi Usaha Tbk. 2020-03-09 2.425.354.179 Pengembangan 2025-04-30 2025-06-04 [6] Yes Yes
952 BESS Batulicin Nusantara Maritim Tb 2020-03-09 3.440.455.528 Pengembangan 2025-03-10 2025-03-19 [10] Yes Yes
953 AMAN Makmur Berkah Amanda Tbk. 2020-03-13 3.873.500.000 Pengembangan NaT NaT <NA> No No
954 CARE Metro Healthcare Indonesia Tbk 2020-03-13 33.250.000.000 Pengembangan NaT NaT <NA> No No

955 rows × 10 columns

Visualisasi Data Bursa

counts = df_emiten_idx["Distress_PPK"].value_counts().reindex(["Yes", "No"], fill_value=0)

# pastikan counts urut Yes lalu No
counts = counts.reindex(["Yes", "No"], fill_value=0)

# warna
soft_red = "#F28B82"
soft_green = "#81C995"
colors = [soft_red, soft_green]

# label persen + jumlah
def autopct_with_count(values):
    total = values.sum()
    def _fmt(pct):
        count = int(round(pct * total / 100.0))
        return f"{pct:.1f}%\n({count})"
    return _fmt

fig, ax = plt.subplots(figsize=(6, 6))
ax.pie(
    counts.values,
    labels=counts.index,
    colors=colors,
    autopct=autopct_with_count(counts),
    startangle=90
)
ax.axis("equal")
ax.set_title("Proporsi & Jumlah Perusahaan Distress_PPK\nYes vs No")

plt.tight_layout()
plt.show()

df = df_emiten_idx.copy()

# status per perusahaan (unik per Kode)
status = (
    df.groupby("Kode")
      .agg(
          pernah_masuk=("Tanggal Masuk", lambda s: s.notna().any()),
          pernah_keluar=("Tanggal Keluar", lambda s: s.notna().any())
      )
)

# ambil hanya perusahaan yg pernah masuk PPK
status = status[status["pernah_masuk"]]

jumlah_masuk_tidak_keluar = int((~status["pernah_keluar"]).sum())
jumlah_masuk_berhasil_keluar = int((status["pernah_keluar"]).sum())

counts = pd.Series(
    {
        "Masuk & tidak keluar": jumlah_masuk_tidak_keluar,
        "Masuk & berhasil keluar": jumlah_masuk_berhasil_keluar
    }
)

# warna
soft_red = "#F28B82"
soft_green = "#81C995"
colors = [soft_red, soft_green]

# bar chart
fig, ax = plt.subplots(figsize=(7, 5))
bars = ax.bar(counts.index, counts.values, color=colors)

ax.set_ylabel("Jumlah perusahaan")
ax.set_title("Jumlah Perusahaan yang Masuk PPK\nBelum Keluar vs Berhasil Keluar")
ax.set_ylim(0, max(counts.values) * 1.15 if max(counts.values) > 0 else 1)

# angka di atas batang
for b in bars:
    h = b.get_height()
    ax.text(b.get_x() + b.get_width()/2, h, f"{int(h)}", ha="center", va="bottom")

plt.tight_layout()
plt.show()

# hitung kemunculan tiap nilai unik (termasuk null)
counts = df_emiten_idx["Papan Pencatatan"].value_counts(dropna=False)

# ganti label null kalau ada
counts.index = counts.index.to_series().astype(str).replace({"nan": "Tidak diketahui"})

total = counts.sum()
pct = counts / total * 100

# label nilai, jumlah, persen
labels = [f"{k}\n{v} ({p:.1f}%)" for k, v, p in zip(counts.index, counts.values, pct.values)]

fig, ax = plt.subplots(figsize=(8, 8))
ax.pie(
    counts.values,
    labels=labels,
    startangle=90
)
ax.axis("equal")
ax.set_title("Jumlah Tiap Jenis Papan")

plt.tight_layout()
plt.show()

df = df_emiten_idx.copy()

# ambil yang punya Kriteria
tmp = df[["Kode", "Kriteria"]].dropna(subset=["Kriteria"]).copy()

# explode list, 1 baris 1 kriteria
exp = tmp.explode("Kriteria")

# rapikan pastikan numerik
exp["Kriteria"] = pd.to_numeric(exp["Kriteria"], errors="coerce")
exp = exp.dropna(subset=["Kriteria"])
exp["Kriteria"] = exp["Kriteria"].astype(int)

# hitung PERUSAHAAN UNIK per kriteria (Kode, Kriteria) unik
unique_pairs = exp.drop_duplicates(subset=["Kode", "Kriteria"])

counts = unique_pairs["Kriteria"].value_counts().sort_index()

# label kriteria, jumlah, persen
total = counts.sum()
percent = counts / total * 100
labels = [f"Kriteria {k}\n{v} ({p:.1f}%)" for k, v, p in zip(counts.index, counts.values, percent.values)]

fig, ax = plt.subplots(figsize=(9, 9))
ax.pie(counts.values, labels=labels, startangle=90)
ax.axis("equal")
ax.set_title("Proporsi Perusahaan per Kriteria PPK")

plt.tight_layout()
plt.show()

df = df_emiten_idx.copy()

# ambil yang punya Kriteria (pernah masuk PPK)
tmp = df[["Kode", "Kriteria"]].dropna(subset=["Kriteria"]).copy()

# explode list, 1 baris 1 kriteria
exp = tmp.explode("Kriteria")

# pastikan numerik bersih
exp["Kriteria"] = pd.to_numeric(exp["Kriteria"], errors="coerce")
exp = exp.dropna(subset=["Kriteria"])
exp["Kriteria"] = exp["Kriteria"].astype(int)

# hitung PERUSAHAAN UNIK per kriteria
# agar 1 perusahaan tidak dihitung dobel untuk kriteria yang sama
unique_pairs = exp.drop_duplicates(subset=["Kode", "Kriteria"])

counts = unique_pairs["Kriteria"].value_counts().sort_index()

# kriteria yg tidak ada perusahaan (0) tetap ditampilkan
full_index = pd.Index(range(1, 12), name="Kriteria")
counts = counts.reindex(full_index, fill_value=0)

# plot bar chart
fig, ax = plt.subplots(figsize=(10, 5))
bars = ax.bar(counts.index.astype(str), counts.values)

ax.set_title("Jumlah Perusahaan per Kriteria PPK")
ax.set_xlabel("Kriteria")
ax.set_ylabel("Jumlah perusahaan")
ax.set_ylim(0, max(counts.values) * 1.15 if max(counts.values) > 0 else 1)

# jumlah
for b in bars:
    h = b.get_height()
    ax.text(b.get_x() + b.get_width()/2, h, f"{int(h)}", ha="center", va="bottom")

plt.tight_layout()
plt.show()

df = df_emiten_idx.copy()

# hitung jumlah perusahaan IPO per tahun (unik per Kode) dan tampilkan semua tahun termasuk yg 0
df["Tanggal Pencatatan"] = pd.to_datetime(df["Tanggal Pencatatan"], errors="coerce")
df["Tahun Pencatatan"] = df["Tanggal Pencatatan"].dt.year

counts_per_year = (
    df.dropna(subset=["Tahun Pencatatan"])
      .groupby("Tahun Pencatatan")["Kode"]
      .nunique()
      .sort_index()
)

min_year = int(df["Tahun Pencatatan"].min())
max_year = int(df["Tahun Pencatatan"].max())
all_years = pd.Index(range(min_year, max_year + 1), name="Tahun Pencatatan")

counts_per_year = counts_per_year.reindex(all_years, fill_value=0)

x = counts_per_year.index.to_numpy()
y = counts_per_year.values.astype(float)

# warna
soft_red = "#F28B82"
soft_green = "#81C995"
cmap = LinearSegmentedColormap.from_list("soft_red_to_green", [soft_red, soft_green])

pos = y[y > 0]
has_positive = len(pos) > 0

if has_positive:
    norm = Normalize(vmin=pos.min(), vmax=pos.max())

def color_for_value(v):
    if v == 0 or (not has_positive):
        return (0, 0, 0, 1)  # hitam kalau tidak ada yang IPO
    return cmap(norm(v))

point_colors = [color_for_value(v) for v in y]

# garis gradasi pakai segments
segments = []
segment_colors = []

subdiv = 30 # jumlah segmen

for i in range(len(x) - 1):
    x0, x1 = x[i], x[i + 1]
    y0, y1 = y[i], y[i + 1]

    xs = np.linspace(x0, x1, subdiv + 1)
    ys = np.linspace(y0, y1, subdiv + 1)

    for j in range(subdiv):
        v_mid = (ys[j] + ys[j + 1]) / 2.0  # nilai untuk warna segmen kecil
        segments.append([[xs[j], ys[j]], [xs[j + 1], ys[j + 1]]])
        segment_colors.append(color_for_value(v_mid))

lc = LineCollection(segments, colors=segment_colors, linewidths=2)

# plot
fig, ax = plt.subplots(figsize=(14, 6))

ax.add_collection(lc) # garis gradasi
ax.scatter(x, y, s=120, c=point_colors, edgecolors="white", linewidths=1.2, zorder=3) # titik besar

ax.set_title("Jumlah Perusahaan Masuk Bursa per Tahun")
ax.set_ylabel("Jumlah perusahaan")

# tampilkan semua tahun vertikal di X
ax.set_xticks(x)
ax.set_xticklabels(x.astype(int), rotation=90, va="center", ha="center")

# tambah jarak
ax.tick_params(axis="x", pad=10) # antara tick label & garis X
ax.set_xlabel("Tahun", labelpad=25) # antara label "Tahun" & tick label

# batas & grid
ax.set_xlim(x.min() - 0.5, x.max() + 0.5)
ax.set_ylim(0, max(y) * 1.15 if max(y) > 0 else 1)
ax.grid(True, alpha=0.3)

# space bawah
plt.subplots_adjust(bottom=0.1)

plt.show()

Financial Data S&P Capital IQ

Load Financial Data

df_raw = pd.read_excel(
    prefix_path + 'Data Dasar SPCIQ SIMPLIFIED (2).xlsx',
    sheet_name="Sheet1",
    header=[0, 1]   # dua baris pertama sebagai header
)

# Beri nama level kolom
df_raw.columns = df_raw.columns.set_names(["variable", "time_flag"])

print("=== df_raw (awal) ===")
display(df_raw.head())

print("=== Contoh nilai time_flag sebelum normalisasi ===")
time_flags_raw = df_raw.columns.get_level_values("time_flag")
print(pd.unique(time_flags_raw.astype(str))[:20])
=== df_raw (awal) ===
variable Entity Name Entity ID Exchange exchange_ticker Primary Industry 1st Level Primary Industry Country / Region Name Address 1 Web Address Business Description Company Type Investor Type Total Liabilities (Rp.M) Total Assets (Rp.M) Company Type Investor Type Company Status Ownership Structure Activist Investor? Yes/No Institution Coverage Level Consolidation Status Has Current Financials? Yes/No Has Bank Regulatory Financials? Yes/No Has Third Party Financials? Yes/No Business Description Long Business Description Topic Tags Crunchbase Categories Year Established Date Established MM/dd/yyyy Year Incorporated Date Incorporated MM/dd/yyyy Month of Fiscal Year End Headcount As Of MM/dd/yyyy Industry Classification Primary Industry 1st Level Primary Industry 2nd Level Primary Industry 3rd Level Primary Industry 4th Level Primary Industry Industry Classification Sector Industry Group Industry Primary Industry City Parent Company Name Parent Ticker Parent Exchange: Ticker Parent Percent Owned (%) Parent City Parent Industry Ultimate Parent Company Name Ultimate Parent Ticker Ultimate Parent Exchange: Ticker Ultimate Parent City Ultimate Parent Industry Ultimate Parent Country / Region All Investors Percent Owned - All Institutions (%) Shares Owned - All Institutions (actual) Market Value - All Institutions (Rp.B) Percent Owned - Insiders (%) Shares Owned - Insiders (actual) Market Value - Insiders (Rp.B) IPO Date MM/dd/yyyy IPO Price (Rp.) Location Type Data Precision Data Year Current Ratio (x) Quick Ratio (x) Working Capital (Rp.M) Total Current Assets (Rp.M) Total Current Liabilities (Rp.M) Inventory (Rp.M) Prepaid Exp. (Rp.M) Total Assets (Rp.M) Total Liabilities (Rp.M) Long-term Debt (Rp.M) Short-term Borrowings (Rp.M) Current Portion of LT Debt & Leases (Rp.M) Total Debt (Rp.M) Total Equity (Rp.M) Net Property, Plant & Equipment (Rp.M) Cost Of Goods Sold (Rp.M) Total Revenue (Rp.M) Operating Income (Rp.M) EBITDA (Rp.M) EBIT (Rp.M) Net Income to Company (Rp.M) Cash from Ops. (Rp.M) Net Change in Cash (Rp.M) ECS Total Common Shares Outstanding (actual) Market Capitalization (Rp.B) Day Close Price (Rp.) Total Capital (Rp.M) Cash & Short-term Investments (Rp.M) Net Intangibles (Rp.M) Current Ratio (x)
time_flag STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 STATIC.1 STATIC.1 STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC.1 STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC.1 STATIC.1 STATIC STATIC STATIC STATIC.1 STATIC STATIC STATIC STATIC.2 STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024.1 2023.1 2022.1 2021.1 2020.1 2019.1 2018.1 2017.1 2016.1 2015.1 2014.1 2013.1 2012.1 2011.1 2010.1 2009.1 2008.1 2024.1 2023.1 2022.1 2021.1 2020.1 2019.1 2018.1 2017.1 2016.1 2015.1 2014.1 2013.1 2012.1 2011.1 2010.1 2009.1 2008.1 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024.1 2023.1 2022.1 2021.1 2020.1 2019.1 2018.1 2017.1 2016.1 2015.1 2014.1 2013.1 2012.1 2011.1 2010.1 2009.1 2008.1
0 Perusahaan Perseroan (Persero) PT Telekomunika... 4210975 IDX IDX:TLKM Integrated Telecommunication Services Technology, Media & Telecommunications Indonesia Jl. Japati No. 1 www.telkom.co.id Perusahaan Perseroan (Persero) PT Telekomunika... Public Company NaN 137185000.000000 130480000.000000 125742000.000000 131785000.000000 126054000.000000 103958000.000000 88893000.000000 86354000.000000 74067000.000000 72745000.000000 55830000.000000 44391000.000000 50527000.000000 42073000.000000 44086000.000000 48228553.000000 47258399.000000 299675000.000000 287042000.000000 274817000.000000 277184000.000000 246943000.000000 221208000.000000 206196000.000000 198484000.000000 179611000.000000 166173000.000000 141822000.000000 127951000.000000 111369000.000000 103054000.000000 100501000.000000 97814160.000000 91256250.000000 Public Company NaN Operating Subsidiary NaN No NaN NaN Yes No No Perusahaan Perseroan (Persero) PT Telekomunika... Perusahaan Perseroan (Persero) PT Telekomunika... Network Backbone; Optical Infrastructure; Clou... Digital Media, Information Technology, Telecom... 1884.000000 27/03/1884 01:00:00 NaN NaT Dec 2025-08-01 Integrated Telecommunication Services Integrated Telecommunication Services Technology, Media & Telecommunications Telecommunication Services Integrated Telecommunication Services NaN Communication Services; Telecommunication Serv... Communication Services Telecommunication Services Diversified Telecommunication Services Integrated Telecommunication Services Bandung PT Danantara Asset Management (Persero) NaN NaN 0.520900 Jakarta Construction and Engineering Indonesia NaN NaN Jakarta Diversified Support Services Indonesia Indonesia (Prior Subsidiary or Operating Unit,... 21.060000 20859696180.000000 77180875.866000 0.090000 85029954.000000 314610.829800 NaT NaN NaN NaN NaN 0.822000 0.777000 0.784000 0.886000 0.673000 0.715000 0.935000 1.048000 1.200000 1.353000 1.061000 1.163000 1.160000 0.958000 0.915000 0.602000 0.542000 0.692000 0.643000 0.630000 0.730000 0.510000 0.543000 0.677000 0.818000 0.993000 1.031000 0.795000 0.974000 0.965000 0.703000 0.687000 0.474000 0.420000 -13687000.000000 -15955000.000000 -15162000.000000 -7854000.000000 -22590000.000000 -16647000.000000 -2993000.000000 2185000.000000 7939000.000000 12499000.000000 1976000.000000 4638000.000000 3866000.000000 -931000.000000 -1744000.000000 -10707101.000000 -12375841.000000 63080000.000000 55613000.000000 55073000.000000 61277000.000000 46503000.000000 41722000.000000 43268000.000000 47561000.000000 47701000.000000 47912000.000000 34294000.000000 33075000.000000 27973000.000000 21258000.000000 18729000.000000 16186024.000000 14622310.000000 76767000.000000 71568000.000000 70235000.000000 69131000.000000 69093000.000000 58369000.000000 46261000.000000 45376000.000000 39762000.000000 35413000.000000 32318000.000000 28437000.000000 24107000.000000 22189000.000000 20473000.000000 26893125.000000 26998151.000000 1096000.000000 997000.000000 1144000.000000 779000.000000 983000.000000 585000.000000 717000.000000 631000.000000 584000.000000 528000.000000 474000.000000 509000.000000 579000.000000 758000.000000 515000.000000 435244.000000 511950.000000 6655000.000000 6520000.000000 7124000.000000 5108000.000000 4993000.000000 5471000.000000 5218000.000000 1576000.000000 1463000.000000 2147000.000000 641000.000000 1186000.000000 849000.000000 731000.000000 883000.000000 722850.000000 1875773.000000 299675000.000000 287042000.000000 274817000.000000 277184000.000000 246943000.000000 221208000.000000 206196000.000000 198484000.000000 179611000.000000 166173000.000000 141822000.000000 127951000.000000 111369000.000000 103054000.000000 100501000.000000 97814160.000000 91256250.000000 137185000.000000 130480000.000000 125742000.000000 131785000.000000 126054000.000000 103958000.000000 88893000.000000 86354000.000000 74067000.000000 72745000.000000 55830000.000000 50527000.000000 44391000.000000 42073000.000000 44086000.000000 48228553.000000 47258399.000000 25518000.000000 27773000.000000 27331000.000000 36319000.000000 30561000.000000 32293000.000000 31410000.000000 24964000.000000 23015000.000000 26229000.000000 11525000.000000 10410000.000000 11803000.000000 12644000.000000 16246000.000000 14249575.000000 11444575.000000 11525000.000000 9650000.000000 8191000.000000 6682000.000000 9934000.000000 8705000.000000 4043000.000000 2289000.000000 911000.000000 602000.000000 1810000.000000 432000.000000 37000.000000 100000.000000 56000.000000 43850.000000 46000.000000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 76868000.000000 68124000.000000 62853000.000000 69078000.000000 65462000.000000 52084000.000000 44087000.000000 35472000.000000 31799000.000000 34612000.000000 23452000.000000 20256000.000000 19275000.000000 17871000.000000 21910000.000000 21329926.000000 17584731.000000 162490000.000000 156562000.000000 149075000.000000 145399000.000000 120889000.000000 117250000.000000 117303000.000000 112130000.000000 105544000.000000 93428000.000000 85992000.000000 77424000.000000 66978000.000000 60981000.000000 56415000.000000 49585607.000000 43997851.000000 207476000.000000 203339000.000000 193022000.000000 183495000.000000 179489000.000000 156973000.000000 143248000.000000 130171000.000000 114498000.000000 103700000.000000 94809000.000000 86761000.000000 77047000.000000 74897000.000000 75832000.000000 76419897.000000 71066244.000000 60010000.000000 58238000.000000 54658000.000000 53530000.000000 50675000.000000 60315000.000000 61252000.000000 53119000.000000 47465000.000000 42893000.000000 35651000.000000 32803000.000000 29602000.000000 27992000.000000 25400000.000000 23565712.000000 22159917.000000 149967000.000000 149216000.000000 147306000.000000 143210000.000000 136462000.000000 135567000.000000 130784000.000000 128256000.000000 116333000.000000 102470000.000000 89696000.000000 82967000.000000 77143000.000000 71253000.000000 69177000.000000 67677518.000000 64166429.000000 43129000.000000 44885000.000000 45202000.000000 43318000.000000 43332000.000000 42283000.000000 38624000.000000 43727000.000000 40000000.000000 33694000.000000 29813000.000000 28589000.000000 26667000.000000 23248000.000000 22894000.000000 23831275.000000 23225173.000000 67694000.000000 70054000.000000 71042000.000000 75032000.000000 65522000.000000 64296000.000000 58804000.000000 63136000.000000 58532000.000000 52228000.000000 46139000.000000 43773000.000000 40876000.000000 37548000.000000 37506000.000000 37787661.000000 35538389.000000 43129000.000000 44885000.000000 45202000.000000 43318000.000000 43332000.000000 42283000.000000 38624000.000000 43727000.000000 40000000.000000 33694000.000000 29813000.000000 28589000.000000 26667000.000000 23248000.000000 22894000.000000 23831275.000000 23225173.000000 30743000.000000 32208000.000000 27720000.000000 34099000.000000 29563000.000000 27592000.000000 26979000.000000 32701000.000000 29172000.000000 23317000.000000 21274000.000000 20290000.000000 18362000.000000 15470000.000000 15870000.000000 16042898.000000 14725429.000000 61600000.000000 60581000.000000 73354000.000000 68353000.000000 65317000.000000 54949000.000000 45671000.000000 49405000.000000 47231000.000000 43669000.000000 37736000.000000 36574000.000000 27941000.000000 30553000.000000 27759000.000000 29811604.000000 24553925.000000 4898000.000000 -2940000.000000 -6364000.000000 17722000.000000 2347000.000000 803000.000000 -7706000.000000 -4622000.000000 1650000.000000 10445000.000000 2976000.000000 1578000.000000 3484000.000000 514000.000000 1315000.000000 915515.000000 -3250846.000000 99062216600.000000 99062216600.000000 99062216600.000000 99062216600.000000 99062216600.000000 99062216600.000000 99062216600.000000 99062216600.000000 99062216600.000000 98198216600.000000 98175853600.000000 97100853600.000000 95745344100.000000 96931696596.000000 98347123896.000000 98347123896.000000 98347123896.000000 268458.606986 391295.755570 371483.312250 400211.355064 327895.936946 393276.999902 371483.312250 439836.241704 394267.622068 304836.025428 281273.820564 208766.835240 173339.797814 137673.219768 156371.926995 185876.064163 135790.870326 2710.000000 3950.000000 3750.000000 4040.000000 3310.000000 3970.000000 3750.000000 4440.000000 3980.000000 3105.000000 2865.000000 2150.000000 1810.000000 1410.000000 1590.000000 1890.000000 1380.000000 239358000.000000 224686000.000000 211928000.000000 214477000.000000 186351000.000000 169334000.000000 161390000.000000 147602000.000000 137343000.000000 128040000.000000 109444000.000000 97680000.000000 86253000.000000 78852000.000000 78325000.000000 70915533.000000 61582582.000000 35027000.000000 30430000.000000 32883000.000000 38740000.000000 21818000.000000 18620000.000000 18590000.000000 26638000.000000 31001000.000000 28566000.000000 18036000.000000 21256000.000000 17428000.000000 9995000.000000 9490000.000000 8164967.000000 7156989.000000 9442000.000000 8731000.000000 8302000.000000 7506000.000000 6846000.000000 6446000.000000 5032000.000000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 0.822000 0.777000 0.784000 0.886000 0.673000 0.715000 0.935000 1.048000 1.200000 1.353000 1.061000 1.163000 1.160000 0.958000 0.915000 0.602000 0.542000
1 PT Abadi Nusantara Hijau Investama Tbk (IDX:PACK) 109420637 IDX IDX:PACK Packaging and Materials: Paper and Plastic Materials Indonesia Jl. Jababeka 2 Block C/11-D www.flexypack.com PT Abadi Nusantara Hijau Investama Tbk provide... Public Company NaN 9131.515000 22395.996000 37928.982000 33821.027000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 66918.151000 77129.464000 43686.326000 36584.088000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN Public Company NaN Operating NaN No NaN NaN Yes No No PT Abadi Nusantara Hijau Investama Tbk provide... PT Abadi Nusantara Hijau Investama Tbk provide... Packaging Services NaN 2019.000000 2019-11-18 00:00:00 NaN NaT Dec NaT Packaging and Materials: Paper and Plastic Packaging and Materials: Paper and Plastic Materials Packaging and Materials: Paper and Plastic NaN NaN Materials; Materials; Containers and Packaging... Materials Materials Containers and Packaging Paper and Plastic Packaging Products and Mater... Bekasi NaN NaN NaN NA% NaN NaN NaN NaN NaN NaN NaN NaN PT Benson Kapital Indonesia (Current Investmen... 23.240000 371188000.000000 965088.800000 0.000000 0.000000 NaN 2023-02-06 162.000000 NaN NaN NaN 3.171000 1.849000 0.397000 0.279000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2.532000 0.891000 0.259000 0.116000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 18470.909000 16160.048000 -15748.231000 -13908.977000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 26979.387000 35187.194000 10379.496000 5381.327000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 8508.479000 19027.146000 26127.727000 19290.304000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 5217.791000 18152.157000 2309.696000 1102.312000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 28.664000 14.360000 4.889000 16.514000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 66918.151000 77129.464000 43686.326000 36584.088000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 9131.515000 22395.996000 37928.982000 33821.027000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2835.577000 9595.659000 15429.582000 17861.356000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 57786.636000 54733.468000 5757.344000 2763.062000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 29813.794000 27877.647000 26738.757000 25626.326000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 40843.331000 37002.120000 32303.664000 30325.181000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 54702.368000 52931.736000 44150.214000 36215.903000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2629.162000 5081.433000 3948.041000 -4738.235000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 4465.941000 6785.251000 5446.039000 -3352.747000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2629.162000 5081.433000 3948.041000 -4738.235000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 899.758000 2288.792000 2052.793000 -4690.516000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 8343.775000 -26767.823000 7568.441000 1967.910000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN -2271.124000 5946.648000 539.116000 -32.844000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1543043824.000000 1537579370.000000 1229546000.000000 571400000.000000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 948.817070 55.351773 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 615.000000 36.000000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 60622.214000 64329.128000 21186.926000 20624.418000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 4249.362000 6520.486000 573.838000 34.722000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 10041.786000 9556.735000 5626.787000 4095.608000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 3.171000 1.849000 0.397000 0.279000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
2 PT ABM Investama Tbk (IDX:ABMM) 4980353 IDX IDX:ABMM Coal and Consumable Fuels Energy and Utilities Indonesia Tiara Marga Trakindo I building www.abm-investama.com PT ABM Investama Tbk, together with its subsid... Public Company NaN 20093470.573188 21545086.944192 21197974.640881 9685324.903374 9268917.351480 8427827.077252 8713242.360857 11934035.187236 12314807.459430 14025286.606475 11507764.542355 8840383.482359 10840297.762755 7019323.859661 3763830.000000 3099452.000000 4360950.000000 33732579.888996 33243187.213530 30787462.520356 14769924.153273 11521759.429112 11820817.650070 12287671.907708 14134486.114136 14460055.871406 16420535.888287 14028411.858435 14757757.263945 12175363.933647 10077243.517333 4890266.000000 4199977.000000 3549129.000000 Public Company NaN Operating Subsidiary NaN No NaN NaN Yes No No PT ABM Investama Tbk, together with its subsid... PT ABM Investama Tbk operating in the sectors ... Clean Energy; Freight Service; Marine Transpor... Energy, Precious Metals 2006.000000 2006-06-01 00:00:00 NaN NaT Dec 2025-09-01 Coal and Consumable Fuels Coal and Consumable Fuels Energy and Utilities Oil, Gas and Coal Coal and Consumable Fuels NaN Energy; Energy; Oil, Gas and Consumable Fuels;... Energy Energy Oil, Gas and Consumable Fuels Coal and Consumable Fuels Jakarta PT Tiara Marga Trakindo NaN NaN 0.535500 Jakarta Industrial Conglomerates PT Tiara Marga Trakindo NaN NaN Jakarta Industrial Conglomerates Indonesia PT Tiara Marga Trakindo (Current Subsidiary or... 0.830000 22926501.000000 68779.503000 7.340000 202136500.000000 606409.500000 2011-12-05 3750.000000 NaN NaN NaN 1.067000 0.986000 1.145000 1.652000 1.196000 1.204000 1.460000 1.597000 0.792000 0.883000 0.982000 0.996000 1.226000 1.362000 0.893000 1.113000 0.592000 0.905000 0.844000 0.792000 1.341000 0.969000 0.992000 1.275000 1.328000 0.653000 0.751000 0.820000 0.813000 1.007000 1.088000 0.700000 0.879000 0.430000 540116.972988 -138088.846896 1393425.586718 2785291.548858 716862.070496 610980.039406 1477581.268563 1720113.072412 -1048360.536690 -622368.364089 -75076.681040 -19818.463770 820288.622790 1134634.650895 -252891.000000 203475.000000 -953832.000000 8603535.372060 9598638.433986 10973792.911797 7055017.392177 4375629.087504 3608327.316374 4690972.433270 4601979.099836 4002183.122758 4692284.395771 4203180.206570 4670518.461795 4449825.549909 4270489.900468 2106194.000000 2001334.000000 1384649.000000 8063418.399072 9736727.280882 9580367.325079 4269725.843319 3658767.017008 2997347.276968 3213391.164707 2881866.027424 5050543.659448 5314652.759860 4278256.887610 4690336.925565 3629536.927119 3135855.249573 2359085.000000 1797859.000000 2338481.000000 976088.594994 821228.155566 864077.600244 565420.432554 525300.327976 411458.649184 441376.280596 406041.471732 345572.528640 293631.497503 310286.074145 499804.401930 442934.444820 414473.810135 301356.000000 233936.000000 241667.000000 77287.962918 47921.324472 68165.694646 145468.209915 43412.280696 124924.565348 78087.463339 208496.797292 304715.857400 359407.215314 308703.011060 234000.429255 171860.938376 26318.699181 11526.000000 NaN NaN 33732579.888996 33243187.213530 30787462.520356 14769924.153273 11521759.429112 11820817.650070 12287671.907708 14134486.114136 14460055.871406 16420535.888287 14028411.858435 14757757.263945 12175363.933647 10077243.517333 4890266.000000 4199977.000000 3549129.000000 20093470.573188 21545086.944192 21197974.640881 9685324.903374 9268917.351480 8427827.077252 8713242.360857 11934035.187236 12314807.459430 14025286.606475 11507764.542355 10840297.762755 8840383.482359 7019323.859661 3763830.000000 3099452.000000 4360950.000000 10650442.128438 10137708.047964 10408734.918322 4363219.444311 4815187.839656 4751102.996552 4920968.773924 6569397.072784 4716889.153288 5460877.970722 5966403.514665 4363521.366120 3501077.172306 2832092.967225 841659.000000 NaN NaN 1725644.443698 1070033.159496 932422.887775 66960.900000 557120.000000 484330.000000 NaN NaN 579496.717636 675321.089765 323324.159965 740148.951675 520493.454485 494015.259308 292208.000000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 15716952.113922 16033280.386032 14577725.216805 5488144.613091 5924993.624080 5400701.282240 5215817.182160 7009762.688972 7134999.566212 8304395.601394 8039725.363235 7610896.646745 6493573.658040 4929981.462200 1924066.000000 0.000000 0.000000 13639109.315808 11698100.269338 9589487.879475 5084599.249899 2252842.077632 3392990.572818 3574429.546851 2200450.926900 2145248.411976 2395249.281812 2520647.316080 3917459.501190 3334980.451288 3057919.657672 1126436.000000 1100525.000000 -811821.000000 13588532.603310 13083527.221044 10798972.554367 6404601.223980 5698003.631872 5952020.777318 6748196.663272 8540482.052180 9309009.686380 10701896.185132 8731295.576750 8657844.015315 6588204.391554 4881836.576369 2172994.000000 1532725.000000 1469802.000000 16940553.969294 16775091.191981 13717060.583375 9379940.152987 7360843.103808 6853297.177677 8527758.336357 7217935.093074 6164493.688407 7070612.704500 6865298.865170 6504601.420018 6719153.515715 5237512.675191 3789687.000000 3455803.000000 3114382.000000 19019676.227701 22748651.299773 21468049.600463 14622288.077965 8840833.965492 8378371.179746 11008635.659721 9244047.765335 7868310.286515 8775353.280985 8599050.952989 8115915.925543 8311255.281959 6607997.885733 4486419.000000 3926320.000000 3235172.000000 1405297.314532 4260726.217364 6039382.223438 4531608.021944 359223.936602 809280.825757 2132909.657311 763888.620366 695084.433659 85615.766681 -781424.815807 327174.492051 466344.893687 706361.557448 271266.000000 491429.000000 -469841.000000 3736427.208202 6016608.771238 7545384.213516 5399394.243944 1591016.404358 1645292.178698 2653600.347789 1987378.442476 2021789.251641 1549664.004501 1237520.491127 1299731.078470 1614985.150831 1250603.533610 705326.000000 NaN NaN 1405297.314532 4260726.217364 6039382.223438 4531608.021944 359223.936602 809280.825757 2132909.657311 763888.620366 695084.433659 85615.766681 -781424.815807 327174.492051 466344.893687 706361.557448 271266.000000 491429.000000 -469841.000000 2208286.963756 4809124.839499 5077732.593725 2664181.082945 -550213.689470 55073.183258 957348.122798 50838.656519 91939.054880 -607731.656746 -1373861.491832 21660.865098 116452.543303 484431.520834 127376.000000 -11346.000000 -3913.000000 5673781.353325 5901279.191712 6262674.404548 5216623.032704 1929513.838089 850217.381364 1934408.478086 2018788.409131 1602614.652688 2292600.227669 1330187.486686 1715558.666979 1249385.895957 53032.416782 612476.000000 678998.000000 -4564.000000 -289365.113950 -502652.039715 -232487.728337 1831719.103847 106265.604590 -595842.773237 47878.928040 789290.497254 -408173.515297 107928.957648 169184.976267 -192036.292693 -708061.937615 1190873.399695 -181536.000000 455151.000000 7861.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 825760000.000000 826000000.000000 NaN 9746.204100 9360.761000 9030.381200 3909.494300 2092.405400 4212.342450 6249.684550 6332.279500 5588.924950 7984.178500 8397.153250 8259.495000 8259.495000 10530.856125 NaN NaN NaN 3540.000000 3400.000000 3280.000000 1420.000000 760.000000 1530.000000 2270.000000 2300.000000 2030.000000 2900.000000 3050.000000 3000.000000 3000.000000 3825.000000 NaN NaN NaN 29356061.429730 27731380.655370 24167213.096280 10572743.862990 8177835.701712 8793691.855058 8790246.729011 9210213.615872 9280247.978188 10699644.883206 10560372.679315 11528356.147935 9828554.109328 7987901.119872 3050502.000000 1100525.000000 -811821.000000 2741781.997248 2907757.321224 3441719.228344 3380709.965967 1522129.237896 1410416.880994 2265170.861332 1907200.309552 1242565.677740 1930012.824268 1526592.624775 1256331.810840 1233004.384532 1669222.334305 433039.000000 614575.000000 159424.000000 241953.535626 216885.738972 251403.717345 223331.241996 236740.734304 240865.597962 45742.429116 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1.067000 0.986000 1.145000 1.652000 1.196000 1.204000 1.460000 1.597000 0.792000 0.883000 0.982000 0.996000 1.226000 1.362000 0.893000 1.113000 0.592000
3 PT Ace Oldfields Tbk (IDX:KUAS) 7649048 IDX IDX:KUAS Building Products Industrials Indonesia Jl. Raya Cileungsi Jonggol www.aceoldfields.com PT Ace Oldfields Tbk manufactures and sells pa... Public Company NaN 115267.049000 107904.478000 120390.576000 122407.148000 155435.120000 154466.505000 161107.849000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 278143.466000 264359.356000 269542.435000 262419.786000 206482.511000 199903.930000 203429.610000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN Public Company NaN Operating NaN No NaN NaN Yes No No PT Ace Oldfields Tbk manufactures and sells pa... NaN NaN NaN 1989.000000 1989-09-18 00:00:00 NaN NaT Dec 2025-09-01 Building Products; Hardware Tools and Equipmen... Building Products Industrials Capital Goods Building Products NaN Industrials; Capital Goods; Building Products;... Industrials Capital Goods Building Products Building Products Bogor NaN NaN NaN NA% NaN NaN NaN NaN NaN NaN NaN NaN Oldfields Holdings Limited (Prior Investment, ... NaN NaN NaN 0.870000 11225400.000000 763.327200 2021-10-25 195.000000 NaN NaN NaN 1.997000 1.974000 1.828000 1.814000 1.308000 1.309000 1.310000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1.172000 1.072000 1.033000 1.210000 0.699000 0.537000 0.544000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 86839.444000 78355.409000 74169.643000 72372.271000 23870.410000 22439.724000 23621.044000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 173948.142000 158762.285000 163700.210000 161289.971000 101493.623000 94943.288000 99749.278000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 87108.698000 80406.876000 89530.566000 88917.700000 77623.213000 72503.564000 76128.234000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 71327.348000 71649.956000 70940.942000 53641.362000 47231.311000 56009.830000 58268.430000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 147.189000 224.208000 244.110000 NaN NaN NaN 35.264000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 278143.466000 264359.356000 269542.435000 262419.786000 206482.511000 199903.930000 203429.610000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 115267.049000 107904.478000 120390.576000 122407.148000 155435.120000 154466.505000 161107.849000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1634.488000 1073.554000 1449.214000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 62574.148000 60824.332000 68829.023000 70330.607000 57947.634000 58677.517000 58922.593000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 89651.801000 87684.384000 99772.844000 100011.914000 133144.255000 138006.077000 141918.039000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 162876.417000 156454.878000 149151.859000 140012.638000 51047.391000 45437.426000 42321.761000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 100287.643000 101849.807000 102292.856000 97649.202000 101747.189000 102175.913000 101709.941000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 130570.138000 114707.655000 112090.876000 106484.602000 103949.885000 107761.935000 113631.570000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 184692.659000 161660.709000 153828.141000 139351.625000 131083.726000 130073.784000 136605.549000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 20523.971000 18341.355000 15142.784000 15351.764000 12508.497000 9495.797000 9791.271000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 24221.391000 22206.902000 18812.802000 18288.391000 14108.647000 10873.891000 11133.338000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 20523.971000 18341.355000 15142.784000 15351.764000 12508.497000 9495.797000 9791.271000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 11300.748000 9505.923000 7864.938000 6178.641000 5750.754000 3591.997000 2840.922000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 5509.571000 6666.933000 -14257.143000 -4560.385000 11269.340000 7629.488000 5507.511000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1390.124000 -9025.851000 -22833.804000 46687.897000 4214.702000 449.762000 1393.988000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1292808150.000000 1292808150.000000 1292808150.000000 1292570540.000000 630000000.000000 902570540.000000 902570540.000000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 67.226024 64.640407 71.091653 93.065079 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 52.000000 50.000000 55.000000 72.000000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 252528.218000 244139.262000 248924.703000 240024.552000 184191.645000 183443.502000 184239.800000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 26195.870000 24805.746000 33831.597000 56665.401000 9977.504000 5762.802000 5313.040000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1.997000 1.974000 1.828000 1.814000 1.308000 1.309000 1.310000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
4 PT Acset Indonusa Tbk (IDX:ACST) 4990054 IDX IDX:ACST Construction and Engineering Industrials Indonesia ACSET Building www.acset.co PT Acset Indonusa Tbk provides construction an... Public Company NaN 2953727.000000 2212725.000000 1440027.000000 1362982.000000 2731074.000000 10160043.000000 7509598.000000 3869352.000000 1201946.000000 1264639.000000 831601.000000 536559.738000 737915.640000 193550.159000 96961.292000 NaN NaN 2812734.000000 2608782.000000 2111024.000000 2478713.000000 3055106.000000 10446519.000000 8936391.000000 5306479.000000 2503171.000000 1929498.000000 1473649.000000 1298358.203000 754771.051000 359091.317000 226443.835000 NaN NaN Public Company NaN Operating Subsidiary NaN No NaN NaN Yes No No PT Acset Indonusa Tbk provides construction an... PT Acset Indonusa Tbk, an integrated construct... NaN Civil Engineering, Construction 1995.000000 1995-01-10 00:00:00 1995.000000 1995-01-10 Dec 2025-09-01 Construction and Engineering; Commercial Const... Construction and Engineering Industrials Capital Goods Construction and Engineering NaN Industrials; Capital Goods; Construction and E... Industrials Capital Goods Construction and Engineering Construction and Engineering Jakarta PT Karya Supra Perkasa NaN NaN 0.501000 NaN Unclassified Jardine Matheson Holdings Limited J36 SGX:J36 Hamilton Industrial Conglomerates Bermuda PT Karya Supra Perkasa (Current Subsidiary or ... 0.110000 20245000.000000 2753.320000 NaN NaN NaN 2013-06-20 2500.000000 NaN NaN NaN 0.856000 0.999000 1.150000 1.403000 0.844000 0.946000 1.097000 1.273000 1.796000 1.326000 1.563000 1.484000 1.151000 1.371000 1.504000 NaN NaN 0.750000 0.870000 1.043000 1.141000 0.623000 0.826000 0.794000 0.937000 1.298000 0.897000 0.948000 0.922000 0.750000 0.948000 0.935000 NaN NaN -420134.000000 -3142.000000 209226.000000 519658.000000 -409901.000000 -538088.000000 717200.000000 1010675.000000 927046.000000 391523.000000 437650.000000 346109.300000 79802.352000 70129.762000 47966.205000 NaN NaN 2491320.000000 2166914.000000 1606973.000000 1808369.000000 2210364.000000 9456832.000000 8120252.000000 4717565.000000 2092380.000000 1590910.000000 1214765.000000 1061422.848000 607779.928000 259326.070000 143194.646000 NaN NaN 2911454.000000 2170056.000000 1397747.000000 1288711.000000 2620265.000000 9994920.000000 7403052.000000 3706890.000000 1165334.000000 1199387.000000 777115.000000 715313.548000 527977.576000 189196.308000 95228.441000 NaN NaN 182037.000000 138376.000000 64156.000000 95506.000000 93676.000000 262326.000000 961291.000000 349646.000000 370809.000000 315771.000000 309266.000000 237777.966000 135685.746000 67149.636000 27583.420000 NaN NaN 5679.000000 6666.000000 7352.000000 6104.000000 49626.000000 13146.000000 12480.000000 34602.000000 9532.000000 4593.000000 4094.000000 6015.411000 NaN 191.925000 145.979000 NaN NaN 2812734.000000 2608782.000000 2111024.000000 2478713.000000 3055106.000000 10446519.000000 8936391.000000 5306479.000000 2503171.000000 1929498.000000 1473649.000000 1298358.203000 754771.051000 359091.317000 226443.835000 NaN NaN 2953727.000000 2212725.000000 1440027.000000 1362982.000000 2731074.000000 10160043.000000 7509598.000000 3869352.000000 1201946.000000 1264639.000000 831601.000000 737915.640000 536559.738000 193550.159000 96961.292000 NaN NaN NaN NaN 3341.000000 22160.000000 51397.000000 104117.000000 63083.000000 138669.000000 20327.000000 56147.000000 44362.000000 16885.953000 5039.545000 1967.620000 466.667000 NaN NaN 285000.000000 40000.000000 NaN NaN 245000.000000 817923.000000 2656388.000000 1092179.000000 255000.000000 350000.000000 45337.000000 15961.846000 26338.333000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 285000.000000 43341.000000 22171.000000 51569.000000 1082372.000000 4210330.000000 3183252.000000 1871951.000000 312608.000000 440262.000000 272965.000000 118266.063000 125610.468000 30522.840000 20866.667000 NaN NaN -140993.000000 396057.000000 670997.000000 1115731.000000 324032.000000 286476.000000 1426793.000000 1437127.000000 1301225.000000 664859.000000 642048.000000 560442.562000 218211.313000 165541.157000 129482.543000 NaN NaN 263754.000000 336870.000000 429592.000000 543775.000000 657998.000000 745130.000000 755129.000000 486798.000000 370306.000000 310061.000000 242007.000000 220839.892000 134582.169000 89379.957000 80250.650000 NaN NaN 3371726.000000 2389679.000000 1348817.000000 1642358.000000 1500270.000000 4046981.000000 3026009.000000 2561089.000000 1514363.000000 1132494.000000 1101969.000000 818537.622000 554357.668000 356616.043000 244572.004000 NaN NaN 3172312.000000 2349638.000000 1036870.000000 1494671.000000 1204429.000000 3947173.000000 3725296.000000 3026989.000000 1794002.000000 1356868.000000 1350908.000000 1014502.030000 669905.664000 429063.355000 303107.369000 NaN NaN -363885.000000 -190416.000000 -464514.000000 -614271.000000 -1082033.000000 -430375.000000 497768.000000 310722.000000 182096.000000 130916.000000 176807.000000 146399.359000 77033.290000 48495.165000 42562.582000 NaN NaN -271954.000000 -88291.000000 -354321.000000 -497358.000000 -957036.000000 -306643.000000 621841.000000 397442.000000 243054.000000 180822.000000 234231.000000 194155.134000 102386.835000 68401.903000 56844.284000 NaN NaN -363885.000000 -190416.000000 -464514.000000 -614271.000000 -1082033.000000 -430375.000000 497768.000000 310722.000000 182096.000000 130916.000000 176807.000000 146399.359000 77033.290000 48495.165000 42562.582000 NaN NaN -542065.000000 -276638.000000 -451613.000000 -693366.000000 -1340079.000000 -1131849.000000 21419.000000 153791.000000 67555.000000 42222.000000 103897.000000 99215.342000 52233.546000 36486.248000 27770.706000 NaN NaN -122713.000000 -101705.000000 -216864.000000 197089.000000 1761692.000000 -341724.000000 -857235.000000 -1128265.000000 -158255.000000 -24968.000000 -43287.000000 -112217.697000 44407.666000 30836.348000 -19897.347000 NaN NaN 129384.000000 92868.000000 -265851.000000 398257.000000 -108366.000000 -40888.000000 7535.000000 75904.000000 78544.000000 13831.000000 1788.000000 -19912.184000 44619.830000 3503.348000 -7196.844000 NaN NaN 12675160000.000000 12675160000.000000 12675160000.000000 12675160000.000000 6425160000.000000 700000000.000000 700000000.000000 700000000.000000 700000000.000000 500000000.000000 500000000.000000 500000000.000000 400000000.000000 400000000.000000 400000000.000000 NaN NaN 1090.063760 1723.821760 1990.000120 2661.783600 2827.070400 679.000000 1088.500000 1722.000000 1974.000000 1510.000000 1862.500000 995.000000 NaN NaN NaN NaN NaN 86.000000 136.000000 157.000000 210.000000 440.000000 970.000000 1555.000000 2460.000000 2820.000000 3020.000000 3725.000000 1990.000000 NaN NaN NaN NaN NaN 144007.000000 439398.000000 693168.000000 1167300.000000 1406404.000000 4496806.000000 4610045.000000 3309078.000000 1613833.000000 1105121.000000 915013.000000 678708.625000 343821.782000 196063.997000 150349.210000 NaN NaN 428058.000000 298674.000000 205806.000000 471657.000000 73400.000000 181766.000000 222654.000000 215119.000000 139215.000000 60671.000000 49575.000000 48718.694000 64965.045000 20345.215000 16841.867000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 0.856000 0.999000 1.150000 1.403000 0.844000 0.946000 1.097000 1.273000 1.796000 1.326000 1.563000 1.484000 1.151000 1.371000 1.504000 NaN NaN
=== Contoh nilai time_flag sebelum normalisasi ===
['STATIC' '2024' '2023' '2022' '2021' '2020' '2019' '2018' '2017' '2016'
 '2015' '2014' '2013' '2012' '2011' '2010' '2009' '2008' 'STATIC.1'
 'STATIC.2']
def normalize_time_flag(x):
    if pd.isna(x):
        return x
    x_str = str(x)
    if x_str == "STATIC":
        return "STATIC"
    # "2024.1" -> "2024", "2024.0" -> "2024"
    x_str = x_str.split(".")[0]
    return x_str

var_level = df_raw.columns.get_level_values("variable")
flag_raw  = df_raw.columns.get_level_values("time_flag")

flag_norm = [normalize_time_flag(v) for v in flag_raw]

df_raw.columns = pd.MultiIndex.from_arrays(
    [var_level, flag_norm],
    names=["variable", "time_flag"]
)

print("=== Nilai unik time_flag setelah normalisasi ===")
print(pd.unique(df_raw.columns.get_level_values("time_flag")))
=== Nilai unik time_flag setelah normalisasi ===
['STATIC' '2024' '2023' '2022' '2021' '2020' '2019' '2018' '2017' '2016'
 '2015' '2014' '2013' '2012' '2011' '2010' '2009' '2008']
df_raw = df_raw.groupby(
    axis=1,
    level=["variable", "time_flag"],
    sort=False
).first()

df_raw.columns = df_raw.columns.set_names(["variable", "time_flag"])

print("=== df_raw setelah penggabungan kolom duplikat ===")
display(df_raw.head())
/tmp/ipykernel_108742/1584443115.py:1: FutureWarning:

DataFrame.groupby with axis=1 is deprecated. Do `frame.T.groupby(...)` without axis instead.
=== df_raw setelah penggabungan kolom duplikat ===
variable Entity Name Entity ID Exchange exchange_ticker Primary Industry 1st Level Primary Industry Country / Region Name Address 1 Web Address Business Description Company Type Investor Type Total Liabilities (Rp.M) Total Assets (Rp.M) Company Status Ownership Structure Activist Investor? Yes/No Institution Coverage Level Consolidation Status Has Current Financials? Yes/No Has Bank Regulatory Financials? Yes/No Has Third Party Financials? Yes/No Long Business Description Topic Tags Crunchbase Categories Year Established Date Established MM/dd/yyyy Year Incorporated Date Incorporated MM/dd/yyyy Month of Fiscal Year End Headcount As Of MM/dd/yyyy Industry Classification 2nd Level Primary Industry 3rd Level Primary Industry 4th Level Primary Industry Sector Industry Group Industry City Parent Company Name Parent Ticker Parent Exchange: Ticker Parent Percent Owned (%) Parent City Parent Industry Ultimate Parent Company Name Ultimate Parent Ticker Ultimate Parent Exchange: Ticker Ultimate Parent City Ultimate Parent Industry Ultimate Parent Country / Region All Investors Percent Owned - All Institutions (%) Shares Owned - All Institutions (actual) Market Value - All Institutions (Rp.B) Percent Owned - Insiders (%) Shares Owned - Insiders (actual) Market Value - Insiders (Rp.B) IPO Date MM/dd/yyyy IPO Price (Rp.) Location Type Data Precision Data Year Current Ratio (x) Quick Ratio (x) Working Capital (Rp.M) Total Current Assets (Rp.M) Total Current Liabilities (Rp.M) Inventory (Rp.M) Prepaid Exp. (Rp.M) Long-term Debt (Rp.M) Short-term Borrowings (Rp.M) Current Portion of LT Debt & Leases (Rp.M) Total Debt (Rp.M) Total Equity (Rp.M) Net Property, Plant & Equipment (Rp.M) Cost Of Goods Sold (Rp.M) Total Revenue (Rp.M) Operating Income (Rp.M) EBITDA (Rp.M) EBIT (Rp.M) Net Income to Company (Rp.M) Cash from Ops. (Rp.M) Net Change in Cash (Rp.M) ECS Total Common Shares Outstanding (actual) Market Capitalization (Rp.B) Day Close Price (Rp.) Total Capital (Rp.M) Cash & Short-term Investments (Rp.M) Net Intangibles (Rp.M)
time_flag STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008
0 Perusahaan Perseroan (Persero) PT Telekomunika... 4210975 IDX IDX:TLKM Integrated Telecommunication Services Technology, Media & Telecommunications Indonesia Jl. Japati No. 1 www.telkom.co.id Perusahaan Perseroan (Persero) PT Telekomunika... Public Company None 137185000.000000 130480000.000000 125742000.000000 131785000.000000 126054000.000000 103958000.000000 88893000.000000 86354000.000000 74067000.000000 72745000.000000 55830000.000000 44391000.000000 50527000.000000 42073000.000000 44086000.000000 48228553.000000 47258399.000000 299675000.000000 287042000.000000 274817000.000000 277184000.000000 246943000.000000 221208000.000000 206196000.000000 198484000.000000 179611000.000000 166173000.000000 141822000.000000 127951000.000000 111369000.000000 103054000.000000 100501000.000000 97814160.000000 91256250.000000 Operating Subsidiary None No None None Yes No No Perusahaan Perseroan (Persero) PT Telekomunika... Network Backbone; Optical Infrastructure; Clou... Digital Media, Information Technology, Telecom... 1884.000000 27/03/1884 01:00:00 NaN NaT Dec 2025-08-01 Integrated Telecommunication Services Telecommunication Services Integrated Telecommunication Services None Communication Services Telecommunication Services Diversified Telecommunication Services Bandung PT Danantara Asset Management (Persero) None None 0.520900 Jakarta Construction and Engineering Indonesia None None Jakarta Diversified Support Services Indonesia Indonesia (Prior Subsidiary or Operating Unit,... 21.060000 20859696180.000000 77180875.866000 0.090000 85029954.000000 314610.829800 NaT NaN None None None 0.822000 0.777000 0.784000 0.886000 0.673000 0.715000 0.935000 1.048000 1.200000 1.353000 1.061000 1.163000 1.160000 0.958000 0.915000 0.602000 0.542000 0.692000 0.643000 0.630000 0.730000 0.510000 0.543000 0.677000 0.818000 0.993000 1.031000 0.795000 0.974000 0.965000 0.703000 0.687000 0.474000 0.420000 -13687000.000000 -15955000.000000 -15162000.000000 -7854000.000000 -22590000.000000 -16647000.000000 -2993000.000000 2185000.000000 7939000.000000 12499000.000000 1976000.000000 4638000.000000 3866000.000000 -931000.000000 -1744000.000000 -10707101.000000 -12375841.000000 63080000.000000 55613000.000000 55073000.000000 61277000.000000 46503000.000000 41722000.000000 43268000.000000 47561000.000000 47701000.000000 47912000.000000 34294000.000000 33075000.000000 27973000.000000 21258000.000000 18729000.000000 16186024.000000 14622310.000000 76767000.000000 71568000.000000 70235000.000000 69131000.000000 69093000.000000 58369000.000000 46261000.000000 45376000.000000 39762000.000000 35413000.000000 32318000.000000 28437000.000000 24107000.000000 22189000.000000 20473000.000000 26893125.000000 26998151.000000 1096000.000000 997000.000000 1144000.000000 779000.000000 983000.000000 585000.000000 717000.000000 631000.000000 584000.000000 528000.000000 474000.000000 509000.000000 579000.000000 758000.000000 515000.000000 435244.000000 511950.000000 6655000.000000 6520000.000000 7124000.000000 5108000.000000 4993000.000000 5471000.000000 5218000.000000 1576000.000000 1463000.000000 2147000.000000 641000.000000 1186000.000000 849000.000000 731000.000000 883000.000000 722850.000000 1875773.000000 25518000.000000 27773000.000000 27331000.000000 36319000.000000 30561000.000000 32293000.000000 31410000.000000 24964000.000000 23015000.000000 26229000.000000 11525000.000000 10410000.000000 11803000.000000 12644000.000000 16246000.000000 14249575.000000 11444575.000000 11525000.000000 9650000.000000 8191000.000000 6682000.000000 9934000.000000 8705000.000000 4043000.000000 2289000.000000 911000.000000 602000.000000 1810000.000000 432000.000000 37000.000000 100000.000000 56000.000000 43850.000000 46000.000000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 76868000.000000 68124000.000000 62853000.000000 69078000.000000 65462000.000000 52084000.000000 44087000.000000 35472000.000000 31799000.000000 34612000.000000 23452000.000000 20256000.000000 19275000.000000 17871000.000000 21910000.000000 21329926.000000 17584731.000000 162490000.000000 156562000.000000 149075000.000000 145399000.000000 120889000.000000 117250000.000000 117303000.000000 112130000.000000 105544000.000000 93428000.000000 85992000.000000 77424000.000000 66978000.000000 60981000.000000 56415000.000000 49585607.000000 43997851.000000 207476000.000000 203339000.000000 193022000.000000 183495000.000000 179489000.000000 156973000.000000 143248000.000000 130171000.000000 114498000.000000 103700000.000000 94809000.000000 86761000.000000 77047000.000000 74897000.000000 75832000.000000 76419897.000000 71066244.000000 60010000.000000 58238000.000000 54658000.000000 53530000.000000 50675000.000000 60315000.000000 61252000.000000 53119000.000000 47465000.000000 42893000.000000 35651000.000000 32803000.000000 29602000.000000 27992000.000000 25400000.000000 23565712.000000 22159917.000000 149967000.000000 149216000.000000 147306000.000000 143210000.000000 136462000.000000 135567000.000000 130784000.000000 128256000.000000 116333000.000000 102470000.000000 89696000.000000 82967000.000000 77143000.000000 71253000.000000 69177000.000000 67677518.000000 64166429.000000 43129000.000000 44885000.000000 45202000.000000 43318000.000000 43332000.000000 42283000.000000 38624000.000000 43727000.000000 40000000.000000 33694000.000000 29813000.000000 28589000.000000 26667000.000000 23248000.000000 22894000.000000 23831275.000000 23225173.000000 67694000.000000 70054000.000000 71042000.000000 75032000.000000 65522000.000000 64296000.000000 58804000.000000 63136000.000000 58532000.000000 52228000.000000 46139000.000000 43773000.000000 40876000.000000 37548000.000000 37506000.000000 37787661.000000 35538389.000000 43129000.000000 44885000.000000 45202000.000000 43318000.000000 43332000.000000 42283000.000000 38624000.000000 43727000.000000 40000000.000000 33694000.000000 29813000.000000 28589000.000000 26667000.000000 23248000.000000 22894000.000000 23831275.000000 23225173.000000 30743000.000000 32208000.000000 27720000.000000 34099000.000000 29563000.000000 27592000.000000 26979000.000000 32701000.000000 29172000.000000 23317000.000000 21274000.000000 20290000.000000 18362000.000000 15470000.000000 15870000.000000 16042898.000000 14725429.000000 61600000.000000 60581000.000000 73354000.000000 68353000.000000 65317000.000000 54949000.000000 45671000.000000 49405000.000000 47231000.000000 43669000.000000 37736000.000000 36574000.000000 27941000.000000 30553000.000000 27759000.000000 29811604.000000 24553925.000000 4898000.000000 -2940000.000000 -6364000.000000 17722000.000000 2347000.000000 803000.000000 -7706000.000000 -4622000.000000 1650000.000000 10445000.000000 2976000.000000 1578000.000000 3484000.000000 514000.000000 1315000.000000 915515.000000 -3250846.000000 99062216600.000000 99062216600.000000 99062216600.000000 99062216600.000000 99062216600.000000 99062216600.000000 99062216600.000000 99062216600.000000 99062216600.000000 98198216600.000000 98175853600.000000 97100853600.000000 95745344100.000000 96931696596.000000 98347123896.000000 98347123896.000000 98347123896.000000 268458.606986 391295.755570 371483.312250 400211.355064 327895.936946 393276.999902 371483.312250 439836.241704 394267.622068 304836.025428 281273.820564 208766.835240 173339.797814 137673.219768 156371.926995 185876.064163 135790.870326 2710.000000 3950.000000 3750.000000 4040.000000 3310.000000 3970.000000 3750.000000 4440.000000 3980.000000 3105.000000 2865.000000 2150.000000 1810.000000 1410.000000 1590.000000 1890.000000 1380.000000 239358000.000000 224686000.000000 211928000.000000 214477000.000000 186351000.000000 169334000.000000 161390000.000000 147602000.000000 137343000.000000 128040000.000000 109444000.000000 97680000.000000 86253000.000000 78852000.000000 78325000.000000 70915533.000000 61582582.000000 35027000.000000 30430000.000000 32883000.000000 38740000.000000 21818000.000000 18620000.000000 18590000.000000 26638000.000000 31001000.000000 28566000.000000 18036000.000000 21256000.000000 17428000.000000 9995000.000000 9490000.000000 8164967.000000 7156989.000000 9442000.000000 8731000.000000 8302000.000000 7506000.000000 6846000.000000 6446000.000000 5032000.000000 NaN NaN NaN NaN None None None None None None
1 PT Abadi Nusantara Hijau Investama Tbk (IDX:PACK) 109420637 IDX IDX:PACK Packaging and Materials: Paper and Plastic Materials Indonesia Jl. Jababeka 2 Block C/11-D www.flexypack.com PT Abadi Nusantara Hijau Investama Tbk provide... Public Company None 9131.515000 22395.996000 37928.982000 33821.027000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 66918.151000 77129.464000 43686.326000 36584.088000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN Operating None No None None Yes No No PT Abadi Nusantara Hijau Investama Tbk provide... Packaging Services None 2019.000000 2019-11-18 00:00:00 NaN NaT Dec NaT Packaging and Materials: Paper and Plastic Packaging and Materials: Paper and Plastic None None Materials Materials Containers and Packaging Bekasi None None None NA% None None None None None None None None PT Benson Kapital Indonesia (Current Investmen... 23.240000 371188000.000000 965088.800000 0.000000 0.000000 NaN 2023-02-06 162.000000 None None None 3.171000 1.849000 0.397000 0.279000 None None None None None None None NaN None None None None None 2.532000 0.891000 0.259000 0.116000 None None None None None None None NaN None None NaN NaN NaN 18470.909000 16160.048000 -15748.231000 -13908.977000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 26979.387000 35187.194000 10379.496000 5381.327000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 8508.479000 19027.146000 26127.727000 19290.304000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 5217.791000 18152.157000 2309.696000 1102.312000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 28.664000 14.360000 4.889000 16.514000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2835.577000 9595.659000 15429.582000 17861.356000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 57786.636000 54733.468000 5757.344000 2763.062000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 29813.794000 27877.647000 26738.757000 25626.326000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 40843.331000 37002.120000 32303.664000 30325.181000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 54702.368000 52931.736000 44150.214000 36215.903000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2629.162000 5081.433000 3948.041000 -4738.235000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 4465.941000 6785.251000 5446.039000 -3352.747000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2629.162000 5081.433000 3948.041000 -4738.235000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 899.758000 2288.792000 2052.793000 -4690.516000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 8343.775000 -26767.823000 7568.441000 1967.910000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN -2271.124000 5946.648000 539.116000 -32.844000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1543043824.000000 1537579370.000000 1229546000.000000 571400000.000000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 948.817070 55.351773 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 615.000000 36.000000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 60622.214000 64329.128000 21186.926000 20624.418000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 4249.362000 6520.486000 573.838000 34.722000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 10041.786000 9556.735000 5626.787000 4095.608000 NaN NaN NaN NaN NaN NaN NaN None None None None None None
2 PT ABM Investama Tbk (IDX:ABMM) 4980353 IDX IDX:ABMM Coal and Consumable Fuels Energy and Utilities Indonesia Tiara Marga Trakindo I building www.abm-investama.com PT ABM Investama Tbk, together with its subsid... Public Company None 20093470.573188 21545086.944192 21197974.640881 9685324.903374 9268917.351480 8427827.077252 8713242.360857 11934035.187236 12314807.459430 14025286.606475 11507764.542355 8840383.482359 10840297.762755 7019323.859661 3763830.000000 3099452.000000 4360950.000000 33732579.888996 33243187.213530 30787462.520356 14769924.153273 11521759.429112 11820817.650070 12287671.907708 14134486.114136 14460055.871406 16420535.888287 14028411.858435 14757757.263945 12175363.933647 10077243.517333 4890266.000000 4199977.000000 3549129.000000 Operating Subsidiary None No None None Yes No No PT ABM Investama Tbk operating in the sectors ... Clean Energy; Freight Service; Marine Transpor... Energy, Precious Metals 2006.000000 2006-06-01 00:00:00 NaN NaT Dec 2025-09-01 Coal and Consumable Fuels Oil, Gas and Coal Coal and Consumable Fuels None Energy Energy Oil, Gas and Consumable Fuels Jakarta PT Tiara Marga Trakindo None None 0.535500 Jakarta Industrial Conglomerates PT Tiara Marga Trakindo None None Jakarta Industrial Conglomerates Indonesia PT Tiara Marga Trakindo (Current Subsidiary or... 0.830000 22926501.000000 68779.503000 7.340000 202136500.000000 606409.500000 2011-12-05 3750.000000 None None None 1.067000 0.986000 1.145000 1.652000 1.196000 1.204000 1.460000 1.597000 0.792000 0.883000 0.982000 0.996000 1.226000 1.362000 0.893000 1.113000 0.592000 0.905000 0.844000 0.792000 1.341000 0.969000 0.992000 1.275000 1.328000 0.653000 0.751000 0.820000 0.813000 1.007000 1.088000 0.700000 0.879000 0.430000 540116.972988 -138088.846896 1393425.586718 2785291.548858 716862.070496 610980.039406 1477581.268563 1720113.072412 -1048360.536690 -622368.364089 -75076.681040 -19818.463770 820288.622790 1134634.650895 -252891.000000 203475.000000 -953832.000000 8603535.372060 9598638.433986 10973792.911797 7055017.392177 4375629.087504 3608327.316374 4690972.433270 4601979.099836 4002183.122758 4692284.395771 4203180.206570 4670518.461795 4449825.549909 4270489.900468 2106194.000000 2001334.000000 1384649.000000 8063418.399072 9736727.280882 9580367.325079 4269725.843319 3658767.017008 2997347.276968 3213391.164707 2881866.027424 5050543.659448 5314652.759860 4278256.887610 4690336.925565 3629536.927119 3135855.249573 2359085.000000 1797859.000000 2338481.000000 976088.594994 821228.155566 864077.600244 565420.432554 525300.327976 411458.649184 441376.280596 406041.471732 345572.528640 293631.497503 310286.074145 499804.401930 442934.444820 414473.810135 301356.000000 233936.000000 241667.000000 77287.962918 47921.324472 68165.694646 145468.209915 43412.280696 124924.565348 78087.463339 208496.797292 304715.857400 359407.215314 308703.011060 234000.429255 171860.938376 26318.699181 11526.000000 NaN NaN 10650442.128438 10137708.047964 10408734.918322 4363219.444311 4815187.839656 4751102.996552 4920968.773924 6569397.072784 4716889.153288 5460877.970722 5966403.514665 4363521.366120 3501077.172306 2832092.967225 841659.000000 NaN NaN 1725644.443698 1070033.159496 932422.887775 66960.900000 557120.000000 484330.000000 NaN NaN 579496.717636 675321.089765 323324.159965 740148.951675 520493.454485 494015.259308 292208.000000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 15716952.113922 16033280.386032 14577725.216805 5488144.613091 5924993.624080 5400701.282240 5215817.182160 7009762.688972 7134999.566212 8304395.601394 8039725.363235 7610896.646745 6493573.658040 4929981.462200 1924066.000000 0.000000 0.000000 13639109.315808 11698100.269338 9589487.879475 5084599.249899 2252842.077632 3392990.572818 3574429.546851 2200450.926900 2145248.411976 2395249.281812 2520647.316080 3917459.501190 3334980.451288 3057919.657672 1126436.000000 1100525.000000 -811821.000000 13588532.603310 13083527.221044 10798972.554367 6404601.223980 5698003.631872 5952020.777318 6748196.663272 8540482.052180 9309009.686380 10701896.185132 8731295.576750 8657844.015315 6588204.391554 4881836.576369 2172994.000000 1532725.000000 1469802.000000 16940553.969294 16775091.191981 13717060.583375 9379940.152987 7360843.103808 6853297.177677 8527758.336357 7217935.093074 6164493.688407 7070612.704500 6865298.865170 6504601.420018 6719153.515715 5237512.675191 3789687.000000 3455803.000000 3114382.000000 19019676.227701 22748651.299773 21468049.600463 14622288.077965 8840833.965492 8378371.179746 11008635.659721 9244047.765335 7868310.286515 8775353.280985 8599050.952989 8115915.925543 8311255.281959 6607997.885733 4486419.000000 3926320.000000 3235172.000000 1405297.314532 4260726.217364 6039382.223438 4531608.021944 359223.936602 809280.825757 2132909.657311 763888.620366 695084.433659 85615.766681 -781424.815807 327174.492051 466344.893687 706361.557448 271266.000000 491429.000000 -469841.000000 3736427.208202 6016608.771238 7545384.213516 5399394.243944 1591016.404358 1645292.178698 2653600.347789 1987378.442476 2021789.251641 1549664.004501 1237520.491127 1299731.078470 1614985.150831 1250603.533610 705326.000000 NaN NaN 1405297.314532 4260726.217364 6039382.223438 4531608.021944 359223.936602 809280.825757 2132909.657311 763888.620366 695084.433659 85615.766681 -781424.815807 327174.492051 466344.893687 706361.557448 271266.000000 491429.000000 -469841.000000 2208286.963756 4809124.839499 5077732.593725 2664181.082945 -550213.689470 55073.183258 957348.122798 50838.656519 91939.054880 -607731.656746 -1373861.491832 21660.865098 116452.543303 484431.520834 127376.000000 -11346.000000 -3913.000000 5673781.353325 5901279.191712 6262674.404548 5216623.032704 1929513.838089 850217.381364 1934408.478086 2018788.409131 1602614.652688 2292600.227669 1330187.486686 1715558.666979 1249385.895957 53032.416782 612476.000000 678998.000000 -4564.000000 -289365.113950 -502652.039715 -232487.728337 1831719.103847 106265.604590 -595842.773237 47878.928040 789290.497254 -408173.515297 107928.957648 169184.976267 -192036.292693 -708061.937615 1190873.399695 -181536.000000 455151.000000 7861.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 825760000.000000 826000000.000000 NaN 9746.204100 9360.761000 9030.381200 3909.494300 2092.405400 4212.342450 6249.684550 6332.279500 5588.924950 7984.178500 8397.153250 8259.495000 8259.495000 10530.856125 NaN NaN NaN 3540.000000 3400.000000 3280.000000 1420.000000 760.000000 1530.000000 2270.000000 2300.000000 2030.000000 2900.000000 3050.000000 3000.000000 3000.000000 3825.000000 NaN NaN NaN 29356061.429730 27731380.655370 24167213.096280 10572743.862990 8177835.701712 8793691.855058 8790246.729011 9210213.615872 9280247.978188 10699644.883206 10560372.679315 11528356.147935 9828554.109328 7987901.119872 3050502.000000 1100525.000000 -811821.000000 2741781.997248 2907757.321224 3441719.228344 3380709.965967 1522129.237896 1410416.880994 2265170.861332 1907200.309552 1242565.677740 1930012.824268 1526592.624775 1256331.810840 1233004.384532 1669222.334305 433039.000000 614575.000000 159424.000000 241953.535626 216885.738972 251403.717345 223331.241996 236740.734304 240865.597962 45742.429116 NaN NaN NaN NaN None None None None None None
3 PT Ace Oldfields Tbk (IDX:KUAS) 7649048 IDX IDX:KUAS Building Products Industrials Indonesia Jl. Raya Cileungsi Jonggol www.aceoldfields.com PT Ace Oldfields Tbk manufactures and sells pa... Public Company None 115267.049000 107904.478000 120390.576000 122407.148000 155435.120000 154466.505000 161107.849000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 278143.466000 264359.356000 269542.435000 262419.786000 206482.511000 199903.930000 203429.610000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN Operating None No None None Yes No No None None None 1989.000000 1989-09-18 00:00:00 NaN NaT Dec 2025-09-01 Building Products; Hardware Tools and Equipmen... Capital Goods Building Products None Industrials Capital Goods Building Products Bogor None None None NA% None None None None None None None None Oldfields Holdings Limited (Prior Investment, ... NaN NaN NaN 0.870000 11225400.000000 763.327200 2021-10-25 195.000000 None None None 1.997000 1.974000 1.828000 1.814000 1.308000 1.309000 1.310000 None None None None NaN None None None None None 1.172000 1.072000 1.033000 1.210000 0.699000 0.537000 0.544000 None None None None NaN None None NaN NaN NaN 86839.444000 78355.409000 74169.643000 72372.271000 23870.410000 22439.724000 23621.044000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 173948.142000 158762.285000 163700.210000 161289.971000 101493.623000 94943.288000 99749.278000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 87108.698000 80406.876000 89530.566000 88917.700000 77623.213000 72503.564000 76128.234000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 71327.348000 71649.956000 70940.942000 53641.362000 47231.311000 56009.830000 58268.430000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 147.189000 224.208000 244.110000 NaN NaN NaN 35.264000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1634.488000 1073.554000 1449.214000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 62574.148000 60824.332000 68829.023000 70330.607000 57947.634000 58677.517000 58922.593000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 89651.801000 87684.384000 99772.844000 100011.914000 133144.255000 138006.077000 141918.039000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 162876.417000 156454.878000 149151.859000 140012.638000 51047.391000 45437.426000 42321.761000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 100287.643000 101849.807000 102292.856000 97649.202000 101747.189000 102175.913000 101709.941000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 130570.138000 114707.655000 112090.876000 106484.602000 103949.885000 107761.935000 113631.570000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 184692.659000 161660.709000 153828.141000 139351.625000 131083.726000 130073.784000 136605.549000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 20523.971000 18341.355000 15142.784000 15351.764000 12508.497000 9495.797000 9791.271000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 24221.391000 22206.902000 18812.802000 18288.391000 14108.647000 10873.891000 11133.338000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 20523.971000 18341.355000 15142.784000 15351.764000 12508.497000 9495.797000 9791.271000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 11300.748000 9505.923000 7864.938000 6178.641000 5750.754000 3591.997000 2840.922000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 5509.571000 6666.933000 -14257.143000 -4560.385000 11269.340000 7629.488000 5507.511000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1390.124000 -9025.851000 -22833.804000 46687.897000 4214.702000 449.762000 1393.988000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1292808150.000000 1292808150.000000 1292808150.000000 1292570540.000000 630000000.000000 902570540.000000 902570540.000000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 67.226024 64.640407 71.091653 93.065079 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 52.000000 50.000000 55.000000 72.000000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 252528.218000 244139.262000 248924.703000 240024.552000 184191.645000 183443.502000 184239.800000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 26195.870000 24805.746000 33831.597000 56665.401000 9977.504000 5762.802000 5313.040000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN None None None None None None
4 PT Acset Indonusa Tbk (IDX:ACST) 4990054 IDX IDX:ACST Construction and Engineering Industrials Indonesia ACSET Building www.acset.co PT Acset Indonusa Tbk provides construction an... Public Company None 2953727.000000 2212725.000000 1440027.000000 1362982.000000 2731074.000000 10160043.000000 7509598.000000 3869352.000000 1201946.000000 1264639.000000 831601.000000 536559.738000 737915.640000 193550.159000 96961.292000 NaN NaN 2812734.000000 2608782.000000 2111024.000000 2478713.000000 3055106.000000 10446519.000000 8936391.000000 5306479.000000 2503171.000000 1929498.000000 1473649.000000 1298358.203000 754771.051000 359091.317000 226443.835000 NaN NaN Operating Subsidiary None No None None Yes No No PT Acset Indonusa Tbk, an integrated construct... None Civil Engineering, Construction 1995.000000 1995-01-10 00:00:00 1995.000000 1995-01-10 Dec 2025-09-01 Construction and Engineering; Commercial Const... Capital Goods Construction and Engineering None Industrials Capital Goods Construction and Engineering Jakarta PT Karya Supra Perkasa None None 0.501000 None Unclassified Jardine Matheson Holdings Limited J36 SGX:J36 Hamilton Industrial Conglomerates Bermuda PT Karya Supra Perkasa (Current Subsidiary or ... 0.110000 20245000.000000 2753.320000 NaN NaN NaN 2013-06-20 2500.000000 None None None 0.856000 0.999000 1.150000 1.403000 0.844000 0.946000 1.097000 1.273000 1.796000 1.326000 1.563000 1.484000 1.151000 1.371000 1.504000 None None 0.750000 0.870000 1.043000 1.141000 0.623000 0.826000 0.794000 0.937000 1.298000 0.897000 0.948000 0.922000 0.750000 0.948000 0.935000 NaN NaN -420134.000000 -3142.000000 209226.000000 519658.000000 -409901.000000 -538088.000000 717200.000000 1010675.000000 927046.000000 391523.000000 437650.000000 346109.300000 79802.352000 70129.762000 47966.205000 NaN NaN 2491320.000000 2166914.000000 1606973.000000 1808369.000000 2210364.000000 9456832.000000 8120252.000000 4717565.000000 2092380.000000 1590910.000000 1214765.000000 1061422.848000 607779.928000 259326.070000 143194.646000 NaN NaN 2911454.000000 2170056.000000 1397747.000000 1288711.000000 2620265.000000 9994920.000000 7403052.000000 3706890.000000 1165334.000000 1199387.000000 777115.000000 715313.548000 527977.576000 189196.308000 95228.441000 NaN NaN 182037.000000 138376.000000 64156.000000 95506.000000 93676.000000 262326.000000 961291.000000 349646.000000 370809.000000 315771.000000 309266.000000 237777.966000 135685.746000 67149.636000 27583.420000 NaN NaN 5679.000000 6666.000000 7352.000000 6104.000000 49626.000000 13146.000000 12480.000000 34602.000000 9532.000000 4593.000000 4094.000000 6015.411000 NaN 191.925000 145.979000 NaN NaN NaN NaN 3341.000000 22160.000000 51397.000000 104117.000000 63083.000000 138669.000000 20327.000000 56147.000000 44362.000000 16885.953000 5039.545000 1967.620000 466.667000 NaN NaN 285000.000000 40000.000000 NaN NaN 245000.000000 817923.000000 2656388.000000 1092179.000000 255000.000000 350000.000000 45337.000000 15961.846000 26338.333000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 285000.000000 43341.000000 22171.000000 51569.000000 1082372.000000 4210330.000000 3183252.000000 1871951.000000 312608.000000 440262.000000 272965.000000 118266.063000 125610.468000 30522.840000 20866.667000 NaN NaN -140993.000000 396057.000000 670997.000000 1115731.000000 324032.000000 286476.000000 1426793.000000 1437127.000000 1301225.000000 664859.000000 642048.000000 560442.562000 218211.313000 165541.157000 129482.543000 NaN NaN 263754.000000 336870.000000 429592.000000 543775.000000 657998.000000 745130.000000 755129.000000 486798.000000 370306.000000 310061.000000 242007.000000 220839.892000 134582.169000 89379.957000 80250.650000 NaN NaN 3371726.000000 2389679.000000 1348817.000000 1642358.000000 1500270.000000 4046981.000000 3026009.000000 2561089.000000 1514363.000000 1132494.000000 1101969.000000 818537.622000 554357.668000 356616.043000 244572.004000 NaN NaN 3172312.000000 2349638.000000 1036870.000000 1494671.000000 1204429.000000 3947173.000000 3725296.000000 3026989.000000 1794002.000000 1356868.000000 1350908.000000 1014502.030000 669905.664000 429063.355000 303107.369000 NaN NaN -363885.000000 -190416.000000 -464514.000000 -614271.000000 -1082033.000000 -430375.000000 497768.000000 310722.000000 182096.000000 130916.000000 176807.000000 146399.359000 77033.290000 48495.165000 42562.582000 NaN NaN -271954.000000 -88291.000000 -354321.000000 -497358.000000 -957036.000000 -306643.000000 621841.000000 397442.000000 243054.000000 180822.000000 234231.000000 194155.134000 102386.835000 68401.903000 56844.284000 NaN NaN -363885.000000 -190416.000000 -464514.000000 -614271.000000 -1082033.000000 -430375.000000 497768.000000 310722.000000 182096.000000 130916.000000 176807.000000 146399.359000 77033.290000 48495.165000 42562.582000 NaN NaN -542065.000000 -276638.000000 -451613.000000 -693366.000000 -1340079.000000 -1131849.000000 21419.000000 153791.000000 67555.000000 42222.000000 103897.000000 99215.342000 52233.546000 36486.248000 27770.706000 NaN NaN -122713.000000 -101705.000000 -216864.000000 197089.000000 1761692.000000 -341724.000000 -857235.000000 -1128265.000000 -158255.000000 -24968.000000 -43287.000000 -112217.697000 44407.666000 30836.348000 -19897.347000 NaN NaN 129384.000000 92868.000000 -265851.000000 398257.000000 -108366.000000 -40888.000000 7535.000000 75904.000000 78544.000000 13831.000000 1788.000000 -19912.184000 44619.830000 3503.348000 -7196.844000 NaN NaN 12675160000.000000 12675160000.000000 12675160000.000000 12675160000.000000 6425160000.000000 700000000.000000 700000000.000000 700000000.000000 700000000.000000 500000000.000000 500000000.000000 500000000.000000 400000000.000000 400000000.000000 400000000.000000 NaN NaN 1090.063760 1723.821760 1990.000120 2661.783600 2827.070400 679.000000 1088.500000 1722.000000 1974.000000 1510.000000 1862.500000 995.000000 NaN NaN NaN NaN NaN 86.000000 136.000000 157.000000 210.000000 440.000000 970.000000 1555.000000 2460.000000 2820.000000 3020.000000 3725.000000 1990.000000 NaN NaN NaN NaN NaN 144007.000000 439398.000000 693168.000000 1167300.000000 1406404.000000 4496806.000000 4610045.000000 3309078.000000 1613833.000000 1105121.000000 915013.000000 678708.625000 343821.782000 196063.997000 150349.210000 NaN NaN 428058.000000 298674.000000 205806.000000 471657.000000 73400.000000 181766.000000 222654.000000 215119.000000 139215.000000 60671.000000 49575.000000 48718.694000 64965.045000 20345.215000 16841.867000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN None None None None None None
ticker_col = ("exchange_ticker", "STATIC")

mask_valid = df_raw[ticker_col].notna()
df_raw = df_raw[mask_valid].copy()

print("=== Setelah drop baris tanpa exchange_ticker (STATIC) ===")
display(df_raw.head())
print("Jumlah baris (emiten) setelah filter:", len(df_raw))
=== Setelah drop baris tanpa exchange_ticker (STATIC) ===
variable Entity Name Entity ID Exchange exchange_ticker Primary Industry 1st Level Primary Industry Country / Region Name Address 1 Web Address Business Description Company Type Investor Type Total Liabilities (Rp.M) Total Assets (Rp.M) Company Status Ownership Structure Activist Investor? Yes/No Institution Coverage Level Consolidation Status Has Current Financials? Yes/No Has Bank Regulatory Financials? Yes/No Has Third Party Financials? Yes/No Long Business Description Topic Tags Crunchbase Categories Year Established Date Established MM/dd/yyyy Year Incorporated Date Incorporated MM/dd/yyyy Month of Fiscal Year End Headcount As Of MM/dd/yyyy Industry Classification 2nd Level Primary Industry 3rd Level Primary Industry 4th Level Primary Industry Sector Industry Group Industry City Parent Company Name Parent Ticker Parent Exchange: Ticker Parent Percent Owned (%) Parent City Parent Industry Ultimate Parent Company Name Ultimate Parent Ticker Ultimate Parent Exchange: Ticker Ultimate Parent City Ultimate Parent Industry Ultimate Parent Country / Region All Investors Percent Owned - All Institutions (%) Shares Owned - All Institutions (actual) Market Value - All Institutions (Rp.B) Percent Owned - Insiders (%) Shares Owned - Insiders (actual) Market Value - Insiders (Rp.B) IPO Date MM/dd/yyyy IPO Price (Rp.) Location Type Data Precision Data Year Current Ratio (x) Quick Ratio (x) Working Capital (Rp.M) Total Current Assets (Rp.M) Total Current Liabilities (Rp.M) Inventory (Rp.M) Prepaid Exp. (Rp.M) Long-term Debt (Rp.M) Short-term Borrowings (Rp.M) Current Portion of LT Debt & Leases (Rp.M) Total Debt (Rp.M) Total Equity (Rp.M) Net Property, Plant & Equipment (Rp.M) Cost Of Goods Sold (Rp.M) Total Revenue (Rp.M) Operating Income (Rp.M) EBITDA (Rp.M) EBIT (Rp.M) Net Income to Company (Rp.M) Cash from Ops. (Rp.M) Net Change in Cash (Rp.M) ECS Total Common Shares Outstanding (actual) Market Capitalization (Rp.B) Day Close Price (Rp.) Total Capital (Rp.M) Cash & Short-term Investments (Rp.M) Net Intangibles (Rp.M)
time_flag STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008 2024 2023 2022 2021 2020 2019 2018 2017 2016 2015 2014 2013 2012 2011 2010 2009 2008
0 Perusahaan Perseroan (Persero) PT Telekomunika... 4210975 IDX IDX:TLKM Integrated Telecommunication Services Technology, Media & Telecommunications Indonesia Jl. Japati No. 1 www.telkom.co.id Perusahaan Perseroan (Persero) PT Telekomunika... Public Company None 137185000.000000 130480000.000000 125742000.000000 131785000.000000 126054000.000000 103958000.000000 88893000.000000 86354000.000000 74067000.000000 72745000.000000 55830000.000000 44391000.000000 50527000.000000 42073000.000000 44086000.000000 48228553.000000 47258399.000000 299675000.000000 287042000.000000 274817000.000000 277184000.000000 246943000.000000 221208000.000000 206196000.000000 198484000.000000 179611000.000000 166173000.000000 141822000.000000 127951000.000000 111369000.000000 103054000.000000 100501000.000000 97814160.000000 91256250.000000 Operating Subsidiary None No None None Yes No No Perusahaan Perseroan (Persero) PT Telekomunika... Network Backbone; Optical Infrastructure; Clou... Digital Media, Information Technology, Telecom... 1884.000000 27/03/1884 01:00:00 NaN NaT Dec 2025-08-01 Integrated Telecommunication Services Telecommunication Services Integrated Telecommunication Services None Communication Services Telecommunication Services Diversified Telecommunication Services Bandung PT Danantara Asset Management (Persero) None None 0.520900 Jakarta Construction and Engineering Indonesia None None Jakarta Diversified Support Services Indonesia Indonesia (Prior Subsidiary or Operating Unit,... 21.060000 20859696180.000000 77180875.866000 0.090000 85029954.000000 314610.829800 NaT NaN None None None 0.822000 0.777000 0.784000 0.886000 0.673000 0.715000 0.935000 1.048000 1.200000 1.353000 1.061000 1.163000 1.160000 0.958000 0.915000 0.602000 0.542000 0.692000 0.643000 0.630000 0.730000 0.510000 0.543000 0.677000 0.818000 0.993000 1.031000 0.795000 0.974000 0.965000 0.703000 0.687000 0.474000 0.420000 -13687000.000000 -15955000.000000 -15162000.000000 -7854000.000000 -22590000.000000 -16647000.000000 -2993000.000000 2185000.000000 7939000.000000 12499000.000000 1976000.000000 4638000.000000 3866000.000000 -931000.000000 -1744000.000000 -10707101.000000 -12375841.000000 63080000.000000 55613000.000000 55073000.000000 61277000.000000 46503000.000000 41722000.000000 43268000.000000 47561000.000000 47701000.000000 47912000.000000 34294000.000000 33075000.000000 27973000.000000 21258000.000000 18729000.000000 16186024.000000 14622310.000000 76767000.000000 71568000.000000 70235000.000000 69131000.000000 69093000.000000 58369000.000000 46261000.000000 45376000.000000 39762000.000000 35413000.000000 32318000.000000 28437000.000000 24107000.000000 22189000.000000 20473000.000000 26893125.000000 26998151.000000 1096000.000000 997000.000000 1144000.000000 779000.000000 983000.000000 585000.000000 717000.000000 631000.000000 584000.000000 528000.000000 474000.000000 509000.000000 579000.000000 758000.000000 515000.000000 435244.000000 511950.000000 6655000.000000 6520000.000000 7124000.000000 5108000.000000 4993000.000000 5471000.000000 5218000.000000 1576000.000000 1463000.000000 2147000.000000 641000.000000 1186000.000000 849000.000000 731000.000000 883000.000000 722850.000000 1875773.000000 25518000.000000 27773000.000000 27331000.000000 36319000.000000 30561000.000000 32293000.000000 31410000.000000 24964000.000000 23015000.000000 26229000.000000 11525000.000000 10410000.000000 11803000.000000 12644000.000000 16246000.000000 14249575.000000 11444575.000000 11525000.000000 9650000.000000 8191000.000000 6682000.000000 9934000.000000 8705000.000000 4043000.000000 2289000.000000 911000.000000 602000.000000 1810000.000000 432000.000000 37000.000000 100000.000000 56000.000000 43850.000000 46000.000000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 76868000.000000 68124000.000000 62853000.000000 69078000.000000 65462000.000000 52084000.000000 44087000.000000 35472000.000000 31799000.000000 34612000.000000 23452000.000000 20256000.000000 19275000.000000 17871000.000000 21910000.000000 21329926.000000 17584731.000000 162490000.000000 156562000.000000 149075000.000000 145399000.000000 120889000.000000 117250000.000000 117303000.000000 112130000.000000 105544000.000000 93428000.000000 85992000.000000 77424000.000000 66978000.000000 60981000.000000 56415000.000000 49585607.000000 43997851.000000 207476000.000000 203339000.000000 193022000.000000 183495000.000000 179489000.000000 156973000.000000 143248000.000000 130171000.000000 114498000.000000 103700000.000000 94809000.000000 86761000.000000 77047000.000000 74897000.000000 75832000.000000 76419897.000000 71066244.000000 60010000.000000 58238000.000000 54658000.000000 53530000.000000 50675000.000000 60315000.000000 61252000.000000 53119000.000000 47465000.000000 42893000.000000 35651000.000000 32803000.000000 29602000.000000 27992000.000000 25400000.000000 23565712.000000 22159917.000000 149967000.000000 149216000.000000 147306000.000000 143210000.000000 136462000.000000 135567000.000000 130784000.000000 128256000.000000 116333000.000000 102470000.000000 89696000.000000 82967000.000000 77143000.000000 71253000.000000 69177000.000000 67677518.000000 64166429.000000 43129000.000000 44885000.000000 45202000.000000 43318000.000000 43332000.000000 42283000.000000 38624000.000000 43727000.000000 40000000.000000 33694000.000000 29813000.000000 28589000.000000 26667000.000000 23248000.000000 22894000.000000 23831275.000000 23225173.000000 67694000.000000 70054000.000000 71042000.000000 75032000.000000 65522000.000000 64296000.000000 58804000.000000 63136000.000000 58532000.000000 52228000.000000 46139000.000000 43773000.000000 40876000.000000 37548000.000000 37506000.000000 37787661.000000 35538389.000000 43129000.000000 44885000.000000 45202000.000000 43318000.000000 43332000.000000 42283000.000000 38624000.000000 43727000.000000 40000000.000000 33694000.000000 29813000.000000 28589000.000000 26667000.000000 23248000.000000 22894000.000000 23831275.000000 23225173.000000 30743000.000000 32208000.000000 27720000.000000 34099000.000000 29563000.000000 27592000.000000 26979000.000000 32701000.000000 29172000.000000 23317000.000000 21274000.000000 20290000.000000 18362000.000000 15470000.000000 15870000.000000 16042898.000000 14725429.000000 61600000.000000 60581000.000000 73354000.000000 68353000.000000 65317000.000000 54949000.000000 45671000.000000 49405000.000000 47231000.000000 43669000.000000 37736000.000000 36574000.000000 27941000.000000 30553000.000000 27759000.000000 29811604.000000 24553925.000000 4898000.000000 -2940000.000000 -6364000.000000 17722000.000000 2347000.000000 803000.000000 -7706000.000000 -4622000.000000 1650000.000000 10445000.000000 2976000.000000 1578000.000000 3484000.000000 514000.000000 1315000.000000 915515.000000 -3250846.000000 99062216600.000000 99062216600.000000 99062216600.000000 99062216600.000000 99062216600.000000 99062216600.000000 99062216600.000000 99062216600.000000 99062216600.000000 98198216600.000000 98175853600.000000 97100853600.000000 95745344100.000000 96931696596.000000 98347123896.000000 98347123896.000000 98347123896.000000 268458.606986 391295.755570 371483.312250 400211.355064 327895.936946 393276.999902 371483.312250 439836.241704 394267.622068 304836.025428 281273.820564 208766.835240 173339.797814 137673.219768 156371.926995 185876.064163 135790.870326 2710.000000 3950.000000 3750.000000 4040.000000 3310.000000 3970.000000 3750.000000 4440.000000 3980.000000 3105.000000 2865.000000 2150.000000 1810.000000 1410.000000 1590.000000 1890.000000 1380.000000 239358000.000000 224686000.000000 211928000.000000 214477000.000000 186351000.000000 169334000.000000 161390000.000000 147602000.000000 137343000.000000 128040000.000000 109444000.000000 97680000.000000 86253000.000000 78852000.000000 78325000.000000 70915533.000000 61582582.000000 35027000.000000 30430000.000000 32883000.000000 38740000.000000 21818000.000000 18620000.000000 18590000.000000 26638000.000000 31001000.000000 28566000.000000 18036000.000000 21256000.000000 17428000.000000 9995000.000000 9490000.000000 8164967.000000 7156989.000000 9442000.000000 8731000.000000 8302000.000000 7506000.000000 6846000.000000 6446000.000000 5032000.000000 NaN NaN NaN NaN None None None None None None
1 PT Abadi Nusantara Hijau Investama Tbk (IDX:PACK) 109420637 IDX IDX:PACK Packaging and Materials: Paper and Plastic Materials Indonesia Jl. Jababeka 2 Block C/11-D www.flexypack.com PT Abadi Nusantara Hijau Investama Tbk provide... Public Company None 9131.515000 22395.996000 37928.982000 33821.027000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 66918.151000 77129.464000 43686.326000 36584.088000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN Operating None No None None Yes No No PT Abadi Nusantara Hijau Investama Tbk provide... Packaging Services None 2019.000000 2019-11-18 00:00:00 NaN NaT Dec NaT Packaging and Materials: Paper and Plastic Packaging and Materials: Paper and Plastic None None Materials Materials Containers and Packaging Bekasi None None None NA% None None None None None None None None PT Benson Kapital Indonesia (Current Investmen... 23.240000 371188000.000000 965088.800000 0.000000 0.000000 NaN 2023-02-06 162.000000 None None None 3.171000 1.849000 0.397000 0.279000 None None None None None None None NaN None None None None None 2.532000 0.891000 0.259000 0.116000 None None None None None None None NaN None None NaN NaN NaN 18470.909000 16160.048000 -15748.231000 -13908.977000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 26979.387000 35187.194000 10379.496000 5381.327000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 8508.479000 19027.146000 26127.727000 19290.304000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 5217.791000 18152.157000 2309.696000 1102.312000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 28.664000 14.360000 4.889000 16.514000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2835.577000 9595.659000 15429.582000 17861.356000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 57786.636000 54733.468000 5757.344000 2763.062000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 29813.794000 27877.647000 26738.757000 25626.326000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 40843.331000 37002.120000 32303.664000 30325.181000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 54702.368000 52931.736000 44150.214000 36215.903000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2629.162000 5081.433000 3948.041000 -4738.235000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 4465.941000 6785.251000 5446.039000 -3352.747000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2629.162000 5081.433000 3948.041000 -4738.235000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 899.758000 2288.792000 2052.793000 -4690.516000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 8343.775000 -26767.823000 7568.441000 1967.910000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN -2271.124000 5946.648000 539.116000 -32.844000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1543043824.000000 1537579370.000000 1229546000.000000 571400000.000000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 948.817070 55.351773 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 615.000000 36.000000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 60622.214000 64329.128000 21186.926000 20624.418000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 4249.362000 6520.486000 573.838000 34.722000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 10041.786000 9556.735000 5626.787000 4095.608000 NaN NaN NaN NaN NaN NaN NaN None None None None None None
2 PT ABM Investama Tbk (IDX:ABMM) 4980353 IDX IDX:ABMM Coal and Consumable Fuels Energy and Utilities Indonesia Tiara Marga Trakindo I building www.abm-investama.com PT ABM Investama Tbk, together with its subsid... Public Company None 20093470.573188 21545086.944192 21197974.640881 9685324.903374 9268917.351480 8427827.077252 8713242.360857 11934035.187236 12314807.459430 14025286.606475 11507764.542355 8840383.482359 10840297.762755 7019323.859661 3763830.000000 3099452.000000 4360950.000000 33732579.888996 33243187.213530 30787462.520356 14769924.153273 11521759.429112 11820817.650070 12287671.907708 14134486.114136 14460055.871406 16420535.888287 14028411.858435 14757757.263945 12175363.933647 10077243.517333 4890266.000000 4199977.000000 3549129.000000 Operating Subsidiary None No None None Yes No No PT ABM Investama Tbk operating in the sectors ... Clean Energy; Freight Service; Marine Transpor... Energy, Precious Metals 2006.000000 2006-06-01 00:00:00 NaN NaT Dec 2025-09-01 Coal and Consumable Fuels Oil, Gas and Coal Coal and Consumable Fuels None Energy Energy Oil, Gas and Consumable Fuels Jakarta PT Tiara Marga Trakindo None None 0.535500 Jakarta Industrial Conglomerates PT Tiara Marga Trakindo None None Jakarta Industrial Conglomerates Indonesia PT Tiara Marga Trakindo (Current Subsidiary or... 0.830000 22926501.000000 68779.503000 7.340000 202136500.000000 606409.500000 2011-12-05 3750.000000 None None None 1.067000 0.986000 1.145000 1.652000 1.196000 1.204000 1.460000 1.597000 0.792000 0.883000 0.982000 0.996000 1.226000 1.362000 0.893000 1.113000 0.592000 0.905000 0.844000 0.792000 1.341000 0.969000 0.992000 1.275000 1.328000 0.653000 0.751000 0.820000 0.813000 1.007000 1.088000 0.700000 0.879000 0.430000 540116.972988 -138088.846896 1393425.586718 2785291.548858 716862.070496 610980.039406 1477581.268563 1720113.072412 -1048360.536690 -622368.364089 -75076.681040 -19818.463770 820288.622790 1134634.650895 -252891.000000 203475.000000 -953832.000000 8603535.372060 9598638.433986 10973792.911797 7055017.392177 4375629.087504 3608327.316374 4690972.433270 4601979.099836 4002183.122758 4692284.395771 4203180.206570 4670518.461795 4449825.549909 4270489.900468 2106194.000000 2001334.000000 1384649.000000 8063418.399072 9736727.280882 9580367.325079 4269725.843319 3658767.017008 2997347.276968 3213391.164707 2881866.027424 5050543.659448 5314652.759860 4278256.887610 4690336.925565 3629536.927119 3135855.249573 2359085.000000 1797859.000000 2338481.000000 976088.594994 821228.155566 864077.600244 565420.432554 525300.327976 411458.649184 441376.280596 406041.471732 345572.528640 293631.497503 310286.074145 499804.401930 442934.444820 414473.810135 301356.000000 233936.000000 241667.000000 77287.962918 47921.324472 68165.694646 145468.209915 43412.280696 124924.565348 78087.463339 208496.797292 304715.857400 359407.215314 308703.011060 234000.429255 171860.938376 26318.699181 11526.000000 NaN NaN 10650442.128438 10137708.047964 10408734.918322 4363219.444311 4815187.839656 4751102.996552 4920968.773924 6569397.072784 4716889.153288 5460877.970722 5966403.514665 4363521.366120 3501077.172306 2832092.967225 841659.000000 NaN NaN 1725644.443698 1070033.159496 932422.887775 66960.900000 557120.000000 484330.000000 NaN NaN 579496.717636 675321.089765 323324.159965 740148.951675 520493.454485 494015.259308 292208.000000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 15716952.113922 16033280.386032 14577725.216805 5488144.613091 5924993.624080 5400701.282240 5215817.182160 7009762.688972 7134999.566212 8304395.601394 8039725.363235 7610896.646745 6493573.658040 4929981.462200 1924066.000000 0.000000 0.000000 13639109.315808 11698100.269338 9589487.879475 5084599.249899 2252842.077632 3392990.572818 3574429.546851 2200450.926900 2145248.411976 2395249.281812 2520647.316080 3917459.501190 3334980.451288 3057919.657672 1126436.000000 1100525.000000 -811821.000000 13588532.603310 13083527.221044 10798972.554367 6404601.223980 5698003.631872 5952020.777318 6748196.663272 8540482.052180 9309009.686380 10701896.185132 8731295.576750 8657844.015315 6588204.391554 4881836.576369 2172994.000000 1532725.000000 1469802.000000 16940553.969294 16775091.191981 13717060.583375 9379940.152987 7360843.103808 6853297.177677 8527758.336357 7217935.093074 6164493.688407 7070612.704500 6865298.865170 6504601.420018 6719153.515715 5237512.675191 3789687.000000 3455803.000000 3114382.000000 19019676.227701 22748651.299773 21468049.600463 14622288.077965 8840833.965492 8378371.179746 11008635.659721 9244047.765335 7868310.286515 8775353.280985 8599050.952989 8115915.925543 8311255.281959 6607997.885733 4486419.000000 3926320.000000 3235172.000000 1405297.314532 4260726.217364 6039382.223438 4531608.021944 359223.936602 809280.825757 2132909.657311 763888.620366 695084.433659 85615.766681 -781424.815807 327174.492051 466344.893687 706361.557448 271266.000000 491429.000000 -469841.000000 3736427.208202 6016608.771238 7545384.213516 5399394.243944 1591016.404358 1645292.178698 2653600.347789 1987378.442476 2021789.251641 1549664.004501 1237520.491127 1299731.078470 1614985.150831 1250603.533610 705326.000000 NaN NaN 1405297.314532 4260726.217364 6039382.223438 4531608.021944 359223.936602 809280.825757 2132909.657311 763888.620366 695084.433659 85615.766681 -781424.815807 327174.492051 466344.893687 706361.557448 271266.000000 491429.000000 -469841.000000 2208286.963756 4809124.839499 5077732.593725 2664181.082945 -550213.689470 55073.183258 957348.122798 50838.656519 91939.054880 -607731.656746 -1373861.491832 21660.865098 116452.543303 484431.520834 127376.000000 -11346.000000 -3913.000000 5673781.353325 5901279.191712 6262674.404548 5216623.032704 1929513.838089 850217.381364 1934408.478086 2018788.409131 1602614.652688 2292600.227669 1330187.486686 1715558.666979 1249385.895957 53032.416782 612476.000000 678998.000000 -4564.000000 -289365.113950 -502652.039715 -232487.728337 1831719.103847 106265.604590 -595842.773237 47878.928040 789290.497254 -408173.515297 107928.957648 169184.976267 -192036.292693 -708061.937615 1190873.399695 -181536.000000 455151.000000 7861.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 2753165000.000000 825760000.000000 826000000.000000 NaN 9746.204100 9360.761000 9030.381200 3909.494300 2092.405400 4212.342450 6249.684550 6332.279500 5588.924950 7984.178500 8397.153250 8259.495000 8259.495000 10530.856125 NaN NaN NaN 3540.000000 3400.000000 3280.000000 1420.000000 760.000000 1530.000000 2270.000000 2300.000000 2030.000000 2900.000000 3050.000000 3000.000000 3000.000000 3825.000000 NaN NaN NaN 29356061.429730 27731380.655370 24167213.096280 10572743.862990 8177835.701712 8793691.855058 8790246.729011 9210213.615872 9280247.978188 10699644.883206 10560372.679315 11528356.147935 9828554.109328 7987901.119872 3050502.000000 1100525.000000 -811821.000000 2741781.997248 2907757.321224 3441719.228344 3380709.965967 1522129.237896 1410416.880994 2265170.861332 1907200.309552 1242565.677740 1930012.824268 1526592.624775 1256331.810840 1233004.384532 1669222.334305 433039.000000 614575.000000 159424.000000 241953.535626 216885.738972 251403.717345 223331.241996 236740.734304 240865.597962 45742.429116 NaN NaN NaN NaN None None None None None None
3 PT Ace Oldfields Tbk (IDX:KUAS) 7649048 IDX IDX:KUAS Building Products Industrials Indonesia Jl. Raya Cileungsi Jonggol www.aceoldfields.com PT Ace Oldfields Tbk manufactures and sells pa... Public Company None 115267.049000 107904.478000 120390.576000 122407.148000 155435.120000 154466.505000 161107.849000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 278143.466000 264359.356000 269542.435000 262419.786000 206482.511000 199903.930000 203429.610000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN Operating None No None None Yes No No None None None 1989.000000 1989-09-18 00:00:00 NaN NaT Dec 2025-09-01 Building Products; Hardware Tools and Equipmen... Capital Goods Building Products None Industrials Capital Goods Building Products Bogor None None None NA% None None None None None None None None Oldfields Holdings Limited (Prior Investment, ... NaN NaN NaN 0.870000 11225400.000000 763.327200 2021-10-25 195.000000 None None None 1.997000 1.974000 1.828000 1.814000 1.308000 1.309000 1.310000 None None None None NaN None None None None None 1.172000 1.072000 1.033000 1.210000 0.699000 0.537000 0.544000 None None None None NaN None None NaN NaN NaN 86839.444000 78355.409000 74169.643000 72372.271000 23870.410000 22439.724000 23621.044000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 173948.142000 158762.285000 163700.210000 161289.971000 101493.623000 94943.288000 99749.278000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 87108.698000 80406.876000 89530.566000 88917.700000 77623.213000 72503.564000 76128.234000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 71327.348000 71649.956000 70940.942000 53641.362000 47231.311000 56009.830000 58268.430000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 147.189000 224.208000 244.110000 NaN NaN NaN 35.264000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1634.488000 1073.554000 1449.214000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 62574.148000 60824.332000 68829.023000 70330.607000 57947.634000 58677.517000 58922.593000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 89651.801000 87684.384000 99772.844000 100011.914000 133144.255000 138006.077000 141918.039000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 162876.417000 156454.878000 149151.859000 140012.638000 51047.391000 45437.426000 42321.761000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 100287.643000 101849.807000 102292.856000 97649.202000 101747.189000 102175.913000 101709.941000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 130570.138000 114707.655000 112090.876000 106484.602000 103949.885000 107761.935000 113631.570000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 184692.659000 161660.709000 153828.141000 139351.625000 131083.726000 130073.784000 136605.549000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 20523.971000 18341.355000 15142.784000 15351.764000 12508.497000 9495.797000 9791.271000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 24221.391000 22206.902000 18812.802000 18288.391000 14108.647000 10873.891000 11133.338000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 20523.971000 18341.355000 15142.784000 15351.764000 12508.497000 9495.797000 9791.271000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 11300.748000 9505.923000 7864.938000 6178.641000 5750.754000 3591.997000 2840.922000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 5509.571000 6666.933000 -14257.143000 -4560.385000 11269.340000 7629.488000 5507.511000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1390.124000 -9025.851000 -22833.804000 46687.897000 4214.702000 449.762000 1393.988000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1292808150.000000 1292808150.000000 1292808150.000000 1292570540.000000 630000000.000000 902570540.000000 902570540.000000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 67.226024 64.640407 71.091653 93.065079 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 52.000000 50.000000 55.000000 72.000000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 252528.218000 244139.262000 248924.703000 240024.552000 184191.645000 183443.502000 184239.800000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 26195.870000 24805.746000 33831.597000 56665.401000 9977.504000 5762.802000 5313.040000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN None None None None None None
4 PT Acset Indonusa Tbk (IDX:ACST) 4990054 IDX IDX:ACST Construction and Engineering Industrials Indonesia ACSET Building www.acset.co PT Acset Indonusa Tbk provides construction an... Public Company None 2953727.000000 2212725.000000 1440027.000000 1362982.000000 2731074.000000 10160043.000000 7509598.000000 3869352.000000 1201946.000000 1264639.000000 831601.000000 536559.738000 737915.640000 193550.159000 96961.292000 NaN NaN 2812734.000000 2608782.000000 2111024.000000 2478713.000000 3055106.000000 10446519.000000 8936391.000000 5306479.000000 2503171.000000 1929498.000000 1473649.000000 1298358.203000 754771.051000 359091.317000 226443.835000 NaN NaN Operating Subsidiary None No None None Yes No No PT Acset Indonusa Tbk, an integrated construct... None Civil Engineering, Construction 1995.000000 1995-01-10 00:00:00 1995.000000 1995-01-10 Dec 2025-09-01 Construction and Engineering; Commercial Const... Capital Goods Construction and Engineering None Industrials Capital Goods Construction and Engineering Jakarta PT Karya Supra Perkasa None None 0.501000 None Unclassified Jardine Matheson Holdings Limited J36 SGX:J36 Hamilton Industrial Conglomerates Bermuda PT Karya Supra Perkasa (Current Subsidiary or ... 0.110000 20245000.000000 2753.320000 NaN NaN NaN 2013-06-20 2500.000000 None None None 0.856000 0.999000 1.150000 1.403000 0.844000 0.946000 1.097000 1.273000 1.796000 1.326000 1.563000 1.484000 1.151000 1.371000 1.504000 None None 0.750000 0.870000 1.043000 1.141000 0.623000 0.826000 0.794000 0.937000 1.298000 0.897000 0.948000 0.922000 0.750000 0.948000 0.935000 NaN NaN -420134.000000 -3142.000000 209226.000000 519658.000000 -409901.000000 -538088.000000 717200.000000 1010675.000000 927046.000000 391523.000000 437650.000000 346109.300000 79802.352000 70129.762000 47966.205000 NaN NaN 2491320.000000 2166914.000000 1606973.000000 1808369.000000 2210364.000000 9456832.000000 8120252.000000 4717565.000000 2092380.000000 1590910.000000 1214765.000000 1061422.848000 607779.928000 259326.070000 143194.646000 NaN NaN 2911454.000000 2170056.000000 1397747.000000 1288711.000000 2620265.000000 9994920.000000 7403052.000000 3706890.000000 1165334.000000 1199387.000000 777115.000000 715313.548000 527977.576000 189196.308000 95228.441000 NaN NaN 182037.000000 138376.000000 64156.000000 95506.000000 93676.000000 262326.000000 961291.000000 349646.000000 370809.000000 315771.000000 309266.000000 237777.966000 135685.746000 67149.636000 27583.420000 NaN NaN 5679.000000 6666.000000 7352.000000 6104.000000 49626.000000 13146.000000 12480.000000 34602.000000 9532.000000 4593.000000 4094.000000 6015.411000 NaN 191.925000 145.979000 NaN NaN NaN NaN 3341.000000 22160.000000 51397.000000 104117.000000 63083.000000 138669.000000 20327.000000 56147.000000 44362.000000 16885.953000 5039.545000 1967.620000 466.667000 NaN NaN 285000.000000 40000.000000 NaN NaN 245000.000000 817923.000000 2656388.000000 1092179.000000 255000.000000 350000.000000 45337.000000 15961.846000 26338.333000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 285000.000000 43341.000000 22171.000000 51569.000000 1082372.000000 4210330.000000 3183252.000000 1871951.000000 312608.000000 440262.000000 272965.000000 118266.063000 125610.468000 30522.840000 20866.667000 NaN NaN -140993.000000 396057.000000 670997.000000 1115731.000000 324032.000000 286476.000000 1426793.000000 1437127.000000 1301225.000000 664859.000000 642048.000000 560442.562000 218211.313000 165541.157000 129482.543000 NaN NaN 263754.000000 336870.000000 429592.000000 543775.000000 657998.000000 745130.000000 755129.000000 486798.000000 370306.000000 310061.000000 242007.000000 220839.892000 134582.169000 89379.957000 80250.650000 NaN NaN 3371726.000000 2389679.000000 1348817.000000 1642358.000000 1500270.000000 4046981.000000 3026009.000000 2561089.000000 1514363.000000 1132494.000000 1101969.000000 818537.622000 554357.668000 356616.043000 244572.004000 NaN NaN 3172312.000000 2349638.000000 1036870.000000 1494671.000000 1204429.000000 3947173.000000 3725296.000000 3026989.000000 1794002.000000 1356868.000000 1350908.000000 1014502.030000 669905.664000 429063.355000 303107.369000 NaN NaN -363885.000000 -190416.000000 -464514.000000 -614271.000000 -1082033.000000 -430375.000000 497768.000000 310722.000000 182096.000000 130916.000000 176807.000000 146399.359000 77033.290000 48495.165000 42562.582000 NaN NaN -271954.000000 -88291.000000 -354321.000000 -497358.000000 -957036.000000 -306643.000000 621841.000000 397442.000000 243054.000000 180822.000000 234231.000000 194155.134000 102386.835000 68401.903000 56844.284000 NaN NaN -363885.000000 -190416.000000 -464514.000000 -614271.000000 -1082033.000000 -430375.000000 497768.000000 310722.000000 182096.000000 130916.000000 176807.000000 146399.359000 77033.290000 48495.165000 42562.582000 NaN NaN -542065.000000 -276638.000000 -451613.000000 -693366.000000 -1340079.000000 -1131849.000000 21419.000000 153791.000000 67555.000000 42222.000000 103897.000000 99215.342000 52233.546000 36486.248000 27770.706000 NaN NaN -122713.000000 -101705.000000 -216864.000000 197089.000000 1761692.000000 -341724.000000 -857235.000000 -1128265.000000 -158255.000000 -24968.000000 -43287.000000 -112217.697000 44407.666000 30836.348000 -19897.347000 NaN NaN 129384.000000 92868.000000 -265851.000000 398257.000000 -108366.000000 -40888.000000 7535.000000 75904.000000 78544.000000 13831.000000 1788.000000 -19912.184000 44619.830000 3503.348000 -7196.844000 NaN NaN 12675160000.000000 12675160000.000000 12675160000.000000 12675160000.000000 6425160000.000000 700000000.000000 700000000.000000 700000000.000000 700000000.000000 500000000.000000 500000000.000000 500000000.000000 400000000.000000 400000000.000000 400000000.000000 NaN NaN 1090.063760 1723.821760 1990.000120 2661.783600 2827.070400 679.000000 1088.500000 1722.000000 1974.000000 1510.000000 1862.500000 995.000000 NaN NaN NaN NaN NaN 86.000000 136.000000 157.000000 210.000000 440.000000 970.000000 1555.000000 2460.000000 2820.000000 3020.000000 3725.000000 1990.000000 NaN NaN NaN NaN NaN 144007.000000 439398.000000 693168.000000 1167300.000000 1406404.000000 4496806.000000 4610045.000000 3309078.000000 1613833.000000 1105121.000000 915013.000000 678708.625000 343821.782000 196063.997000 150349.210000 NaN NaN 428058.000000 298674.000000 205806.000000 471657.000000 73400.000000 181766.000000 222654.000000 215119.000000 139215.000000 60671.000000 49575.000000 48718.694000 64965.045000 20345.215000 16841.867000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN None None None None None None
Jumlah baris (emiten) setelah filter: 924
# mask STATIC
static_mask = df_raw.columns.get_level_values("time_flag") == "STATIC"

df_static = df_raw.loc[:, static_mask].copy()

print("=== df_static (masih 2-level header, semua STATIC) ===")
display(df_static.head())

# flatten
# ambil level 'STATIC' → tinggal nama variable saja
df_static = df_static.xs("STATIC", axis=1, level="time_flag")

print("=== df_static setelah flatten (1 header, 1 baris per emiten) ===")
display(df_static.head())
=== df_static (masih 2-level header, semua STATIC) ===
variable Entity Name Entity ID Exchange exchange_ticker Primary Industry 1st Level Primary Industry Country / Region Name Address 1 Web Address Business Description Company Type Investor Type Company Status Ownership Structure Activist Investor? Yes/No Institution Coverage Level Consolidation Status Has Current Financials? Yes/No Has Bank Regulatory Financials? Yes/No Has Third Party Financials? Yes/No Long Business Description Topic Tags Crunchbase Categories Year Established Date Established MM/dd/yyyy Year Incorporated Date Incorporated MM/dd/yyyy Month of Fiscal Year End Headcount As Of MM/dd/yyyy Industry Classification 2nd Level Primary Industry 3rd Level Primary Industry 4th Level Primary Industry Sector Industry Group Industry City Parent Company Name Parent Ticker Parent Exchange: Ticker Parent Percent Owned (%) Parent City Parent Industry Ultimate Parent Company Name Ultimate Parent Ticker Ultimate Parent Exchange: Ticker Ultimate Parent City Ultimate Parent Industry Ultimate Parent Country / Region All Investors Percent Owned - All Institutions (%) Shares Owned - All Institutions (actual) Market Value - All Institutions (Rp.B) Percent Owned - Insiders (%) Shares Owned - Insiders (actual) Market Value - Insiders (Rp.B) IPO Date MM/dd/yyyy IPO Price (Rp.) Location Type Data Precision Data Year
time_flag STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC STATIC
0 Perusahaan Perseroan (Persero) PT Telekomunika... 4210975 IDX IDX:TLKM Integrated Telecommunication Services Technology, Media & Telecommunications Indonesia Jl. Japati No. 1 www.telkom.co.id Perusahaan Perseroan (Persero) PT Telekomunika... Public Company None Operating Subsidiary None No None None Yes No No Perusahaan Perseroan (Persero) PT Telekomunika... Network Backbone; Optical Infrastructure; Clou... Digital Media, Information Technology, Telecom... 1884.000000 27/03/1884 01:00:00 NaN NaT Dec 2025-08-01 Integrated Telecommunication Services Telecommunication Services Integrated Telecommunication Services None Communication Services Telecommunication Services Diversified Telecommunication Services Bandung PT Danantara Asset Management (Persero) None None 0.520900 Jakarta Construction and Engineering Indonesia None None Jakarta Diversified Support Services Indonesia Indonesia (Prior Subsidiary or Operating Unit,... 21.060000 20859696180.000000 77180875.866000 0.090000 85029954.000000 314610.829800 NaT NaN None None None
1 PT Abadi Nusantara Hijau Investama Tbk (IDX:PACK) 109420637 IDX IDX:PACK Packaging and Materials: Paper and Plastic Materials Indonesia Jl. Jababeka 2 Block C/11-D www.flexypack.com PT Abadi Nusantara Hijau Investama Tbk provide... Public Company None Operating None No None None Yes No No PT Abadi Nusantara Hijau Investama Tbk provide... Packaging Services None 2019.000000 2019-11-18 00:00:00 NaN NaT Dec NaT Packaging and Materials: Paper and Plastic Packaging and Materials: Paper and Plastic None None Materials Materials Containers and Packaging Bekasi None None None NA% None None None None None None None None PT Benson Kapital Indonesia (Current Investmen... 23.240000 371188000.000000 965088.800000 0.000000 0.000000 NaN 2023-02-06 162.000000 None None None
2 PT ABM Investama Tbk (IDX:ABMM) 4980353 IDX IDX:ABMM Coal and Consumable Fuels Energy and Utilities Indonesia Tiara Marga Trakindo I building www.abm-investama.com PT ABM Investama Tbk, together with its subsid... Public Company None Operating Subsidiary None No None None Yes No No PT ABM Investama Tbk operating in the sectors ... Clean Energy; Freight Service; Marine Transpor... Energy, Precious Metals 2006.000000 2006-06-01 00:00:00 NaN NaT Dec 2025-09-01 Coal and Consumable Fuels Oil, Gas and Coal Coal and Consumable Fuels None Energy Energy Oil, Gas and Consumable Fuels Jakarta PT Tiara Marga Trakindo None None 0.535500 Jakarta Industrial Conglomerates PT Tiara Marga Trakindo None None Jakarta Industrial Conglomerates Indonesia PT Tiara Marga Trakindo (Current Subsidiary or... 0.830000 22926501.000000 68779.503000 7.340000 202136500.000000 606409.500000 2011-12-05 3750.000000 None None None
3 PT Ace Oldfields Tbk (IDX:KUAS) 7649048 IDX IDX:KUAS Building Products Industrials Indonesia Jl. Raya Cileungsi Jonggol www.aceoldfields.com PT Ace Oldfields Tbk manufactures and sells pa... Public Company None Operating None No None None Yes No No None None None 1989.000000 1989-09-18 00:00:00 NaN NaT Dec 2025-09-01 Building Products; Hardware Tools and Equipmen... Capital Goods Building Products None Industrials Capital Goods Building Products Bogor None None None NA% None None None None None None None None Oldfields Holdings Limited (Prior Investment, ... NaN NaN NaN 0.870000 11225400.000000 763.327200 2021-10-25 195.000000 None None None
4 PT Acset Indonusa Tbk (IDX:ACST) 4990054 IDX IDX:ACST Construction and Engineering Industrials Indonesia ACSET Building www.acset.co PT Acset Indonusa Tbk provides construction an... Public Company None Operating Subsidiary None No None None Yes No No PT Acset Indonusa Tbk, an integrated construct... None Civil Engineering, Construction 1995.000000 1995-01-10 00:00:00 1995.000000 1995-01-10 Dec 2025-09-01 Construction and Engineering; Commercial Const... Capital Goods Construction and Engineering None Industrials Capital Goods Construction and Engineering Jakarta PT Karya Supra Perkasa None None 0.501000 None Unclassified Jardine Matheson Holdings Limited J36 SGX:J36 Hamilton Industrial Conglomerates Bermuda PT Karya Supra Perkasa (Current Subsidiary or ... 0.110000 20245000.000000 2753.320000 NaN NaN NaN 2013-06-20 2500.000000 None None None
=== df_static setelah flatten (1 header, 1 baris per emiten) ===
variable Entity Name Entity ID Exchange exchange_ticker Primary Industry 1st Level Primary Industry Country / Region Name Address 1 Web Address Business Description Company Type Investor Type Company Status Ownership Structure Activist Investor? Yes/No Institution Coverage Level Consolidation Status Has Current Financials? Yes/No Has Bank Regulatory Financials? Yes/No Has Third Party Financials? Yes/No Long Business Description Topic Tags Crunchbase Categories Year Established Date Established MM/dd/yyyy Year Incorporated Date Incorporated MM/dd/yyyy Month of Fiscal Year End Headcount As Of MM/dd/yyyy Industry Classification 2nd Level Primary Industry 3rd Level Primary Industry 4th Level Primary Industry Sector Industry Group Industry City Parent Company Name Parent Ticker Parent Exchange: Ticker Parent Percent Owned (%) Parent City Parent Industry Ultimate Parent Company Name Ultimate Parent Ticker Ultimate Parent Exchange: Ticker Ultimate Parent City Ultimate Parent Industry Ultimate Parent Country / Region All Investors Percent Owned - All Institutions (%) Shares Owned - All Institutions (actual) Market Value - All Institutions (Rp.B) Percent Owned - Insiders (%) Shares Owned - Insiders (actual) Market Value - Insiders (Rp.B) IPO Date MM/dd/yyyy IPO Price (Rp.) Location Type Data Precision Data Year
0 Perusahaan Perseroan (Persero) PT Telekomunika... 4210975 IDX IDX:TLKM Integrated Telecommunication Services Technology, Media & Telecommunications Indonesia Jl. Japati No. 1 www.telkom.co.id Perusahaan Perseroan (Persero) PT Telekomunika... Public Company None Operating Subsidiary None No None None Yes No No Perusahaan Perseroan (Persero) PT Telekomunika... Network Backbone; Optical Infrastructure; Clou... Digital Media, Information Technology, Telecom... 1884.000000 27/03/1884 01:00:00 NaN NaT Dec 2025-08-01 Integrated Telecommunication Services Telecommunication Services Integrated Telecommunication Services None Communication Services Telecommunication Services Diversified Telecommunication Services Bandung PT Danantara Asset Management (Persero) None None 0.520900 Jakarta Construction and Engineering Indonesia None None Jakarta Diversified Support Services Indonesia Indonesia (Prior Subsidiary or Operating Unit,... 21.060000 20859696180.000000 77180875.866000 0.090000 85029954.000000 314610.829800 NaT NaN None None None
1 PT Abadi Nusantara Hijau Investama Tbk (IDX:PACK) 109420637 IDX IDX:PACK Packaging and Materials: Paper and Plastic Materials Indonesia Jl. Jababeka 2 Block C/11-D www.flexypack.com PT Abadi Nusantara Hijau Investama Tbk provide... Public Company None Operating None No None None Yes No No PT Abadi Nusantara Hijau Investama Tbk provide... Packaging Services None 2019.000000 2019-11-18 00:00:00 NaN NaT Dec NaT Packaging and Materials: Paper and Plastic Packaging and Materials: Paper and Plastic None None Materials Materials Containers and Packaging Bekasi None None None NA% None None None None None None None None PT Benson Kapital Indonesia (Current Investmen... 23.240000 371188000.000000 965088.800000 0.000000 0.000000 NaN 2023-02-06 162.000000 None None None
2 PT ABM Investama Tbk (IDX:ABMM) 4980353 IDX IDX:ABMM Coal and Consumable Fuels Energy and Utilities Indonesia Tiara Marga Trakindo I building www.abm-investama.com PT ABM Investama Tbk, together with its subsid... Public Company None Operating Subsidiary None No None None Yes No No PT ABM Investama Tbk operating in the sectors ... Clean Energy; Freight Service; Marine Transpor... Energy, Precious Metals 2006.000000 2006-06-01 00:00:00 NaN NaT Dec 2025-09-01 Coal and Consumable Fuels Oil, Gas and Coal Coal and Consumable Fuels None Energy Energy Oil, Gas and Consumable Fuels Jakarta PT Tiara Marga Trakindo None None 0.535500 Jakarta Industrial Conglomerates PT Tiara Marga Trakindo None None Jakarta Industrial Conglomerates Indonesia PT Tiara Marga Trakindo (Current Subsidiary or... 0.830000 22926501.000000 68779.503000 7.340000 202136500.000000 606409.500000 2011-12-05 3750.000000 None None None
3 PT Ace Oldfields Tbk (IDX:KUAS) 7649048 IDX IDX:KUAS Building Products Industrials Indonesia Jl. Raya Cileungsi Jonggol www.aceoldfields.com PT Ace Oldfields Tbk manufactures and sells pa... Public Company None Operating None No None None Yes No No None None None 1989.000000 1989-09-18 00:00:00 NaN NaT Dec 2025-09-01 Building Products; Hardware Tools and Equipmen... Capital Goods Building Products None Industrials Capital Goods Building Products Bogor None None None NA% None None None None None None None None Oldfields Holdings Limited (Prior Investment, ... NaN NaN NaN 0.870000 11225400.000000 763.327200 2021-10-25 195.000000 None None None
4 PT Acset Indonusa Tbk (IDX:ACST) 4990054 IDX IDX:ACST Construction and Engineering Industrials Indonesia ACSET Building www.acset.co PT Acset Indonusa Tbk provides construction an... Public Company None Operating Subsidiary None No None None Yes No No PT Acset Indonusa Tbk, an integrated construct... None Civil Engineering, Construction 1995.000000 1995-01-10 00:00:00 1995.000000 1995-01-10 Dec 2025-09-01 Construction and Engineering; Commercial Const... Capital Goods Construction and Engineering None Industrials Capital Goods Construction and Engineering Jakarta PT Karya Supra Perkasa None None 0.501000 None Unclassified Jardine Matheson Holdings Limited J36 SGX:J36 Hamilton Industrial Conglomerates Bermuda PT Karya Supra Perkasa (Current Subsidiary or ... 0.110000 20245000.000000 2753.320000 NaN NaN NaN 2013-06-20 2500.000000 None None None
all_flags = df_raw.columns.get_level_values("time_flag")
years = sorted({
    f for f in all_flags
    if (str(f) != "STATIC") and pd.notna(f)
})

print("=== Daftar Year unik (String) ===")
print(years)
=== Daftar Year unik (String) ===
['2008', '2009', '2010', '2011', '2012', '2013', '2014', '2015', '2016', '2017', '2018', '2019', '2020', '2021', '2022', '2023', '2024']
panel_frames = []

for y in years:
    # Ambil semua kolom yang time_flag = y  (misal 2024)
    df_y = df_raw.xs(y, axis=1, level="time_flag")

    # Gabung dengan kolom STATIC (company_name, industry, dll)
    tmp = pd.concat([df_static, df_y], axis=1)

    # Tambahkan kolom Year
    tmp = tmp.copy()
    # Kalau mau int:
    tmp["Year"] = pd.to_numeric(y, errors="ignore")

    panel_frames.append(tmp)

# Gabungkan semua year jadi 1 DataFrame panjang
df_final = pd.concat(panel_frames, ignore_index=True)

print("=== df_final (panel awal, sebelum filter) ===")
display(df_final.head())
print("Shape df_final:", df_final.shape)
=== df_final (panel awal, sebelum filter) ===
/tmp/ipykernel_108742/1820188604.py:13: FutureWarning:

errors='ignore' is deprecated and will raise in a future version. Use to_numeric without passing `errors` and catch exceptions explicitly instead

/tmp/ipykernel_108742/1820188604.py:13: FutureWarning:

errors='ignore' is deprecated and will raise in a future version. Use to_numeric without passing `errors` and catch exceptions explicitly instead

/tmp/ipykernel_108742/1820188604.py:13: FutureWarning:

errors='ignore' is deprecated and will raise in a future version. Use to_numeric without passing `errors` and catch exceptions explicitly instead

/tmp/ipykernel_108742/1820188604.py:13: FutureWarning:

errors='ignore' is deprecated and will raise in a future version. Use to_numeric without passing `errors` and catch exceptions explicitly instead

/tmp/ipykernel_108742/1820188604.py:13: FutureWarning:

errors='ignore' is deprecated and will raise in a future version. Use to_numeric without passing `errors` and catch exceptions explicitly instead

/tmp/ipykernel_108742/1820188604.py:13: FutureWarning:

errors='ignore' is deprecated and will raise in a future version. Use to_numeric without passing `errors` and catch exceptions explicitly instead

/tmp/ipykernel_108742/1820188604.py:13: FutureWarning:

errors='ignore' is deprecated and will raise in a future version. Use to_numeric without passing `errors` and catch exceptions explicitly instead

/tmp/ipykernel_108742/1820188604.py:13: FutureWarning:

errors='ignore' is deprecated and will raise in a future version. Use to_numeric without passing `errors` and catch exceptions explicitly instead

/tmp/ipykernel_108742/1820188604.py:13: FutureWarning:

errors='ignore' is deprecated and will raise in a future version. Use to_numeric without passing `errors` and catch exceptions explicitly instead

/tmp/ipykernel_108742/1820188604.py:13: FutureWarning:

errors='ignore' is deprecated and will raise in a future version. Use to_numeric without passing `errors` and catch exceptions explicitly instead

/tmp/ipykernel_108742/1820188604.py:13: FutureWarning:

errors='ignore' is deprecated and will raise in a future version. Use to_numeric without passing `errors` and catch exceptions explicitly instead

/tmp/ipykernel_108742/1820188604.py:13: FutureWarning:

errors='ignore' is deprecated and will raise in a future version. Use to_numeric without passing `errors` and catch exceptions explicitly instead

/tmp/ipykernel_108742/1820188604.py:13: FutureWarning:

errors='ignore' is deprecated and will raise in a future version. Use to_numeric without passing `errors` and catch exceptions explicitly instead

/tmp/ipykernel_108742/1820188604.py:13: FutureWarning:

errors='ignore' is deprecated and will raise in a future version. Use to_numeric without passing `errors` and catch exceptions explicitly instead

/tmp/ipykernel_108742/1820188604.py:13: FutureWarning:

errors='ignore' is deprecated and will raise in a future version. Use to_numeric without passing `errors` and catch exceptions explicitly instead

/tmp/ipykernel_108742/1820188604.py:13: FutureWarning:

errors='ignore' is deprecated and will raise in a future version. Use to_numeric without passing `errors` and catch exceptions explicitly instead

/tmp/ipykernel_108742/1820188604.py:13: FutureWarning:

errors='ignore' is deprecated and will raise in a future version. Use to_numeric without passing `errors` and catch exceptions explicitly instead

/tmp/ipykernel_108742/1820188604.py:18: FutureWarning:

The behavior of DataFrame concatenation with empty or all-NA entries is deprecated. In a future version, this will no longer exclude empty or all-NA columns when determining the result dtypes. To retain the old behavior, exclude the relevant entries before the concat operation.
variable Entity Name Entity ID Exchange exchange_ticker Primary Industry 1st Level Primary Industry Country / Region Name Address 1 Web Address Business Description Company Type Investor Type Company Status Ownership Structure Activist Investor? Yes/No Institution Coverage Level Consolidation Status Has Current Financials? Yes/No Has Bank Regulatory Financials? Yes/No Has Third Party Financials? Yes/No Long Business Description Topic Tags Crunchbase Categories Year Established Date Established MM/dd/yyyy Year Incorporated Date Incorporated MM/dd/yyyy Month of Fiscal Year End Headcount As Of MM/dd/yyyy Industry Classification 2nd Level Primary Industry 3rd Level Primary Industry 4th Level Primary Industry Sector Industry Group Industry City Parent Company Name Parent Ticker Parent Exchange: Ticker Parent Percent Owned (%) Parent City Parent Industry Ultimate Parent Company Name Ultimate Parent Ticker Ultimate Parent Exchange: Ticker Ultimate Parent City Ultimate Parent Industry Ultimate Parent Country / Region All Investors Percent Owned - All Institutions (%) Shares Owned - All Institutions (actual) Market Value - All Institutions (Rp.B) Percent Owned - Insiders (%) Shares Owned - Insiders (actual) Market Value - Insiders (Rp.B) IPO Date MM/dd/yyyy IPO Price (Rp.) Location Type Data Precision Data Year Total Liabilities (Rp.M) Total Assets (Rp.M) Current Ratio (x) Quick Ratio (x) Working Capital (Rp.M) Total Current Assets (Rp.M) Total Current Liabilities (Rp.M) Inventory (Rp.M) Prepaid Exp. (Rp.M) Long-term Debt (Rp.M) Short-term Borrowings (Rp.M) Current Portion of LT Debt & Leases (Rp.M) Total Debt (Rp.M) Total Equity (Rp.M) Net Property, Plant & Equipment (Rp.M) Cost Of Goods Sold (Rp.M) Total Revenue (Rp.M) Operating Income (Rp.M) EBITDA (Rp.M) EBIT (Rp.M) Net Income to Company (Rp.M) Cash from Ops. (Rp.M) Net Change in Cash (Rp.M) ECS Total Common Shares Outstanding (actual) Market Capitalization (Rp.B) Day Close Price (Rp.) Total Capital (Rp.M) Cash & Short-term Investments (Rp.M) Net Intangibles (Rp.M) Year
0 Perusahaan Perseroan (Persero) PT Telekomunika... 4210975 IDX IDX:TLKM Integrated Telecommunication Services Technology, Media & Telecommunications Indonesia Jl. Japati No. 1 www.telkom.co.id Perusahaan Perseroan (Persero) PT Telekomunika... Public Company None Operating Subsidiary None No None None Yes No No Perusahaan Perseroan (Persero) PT Telekomunika... Network Backbone; Optical Infrastructure; Clou... Digital Media, Information Technology, Telecom... 1884.000000 27/03/1884 01:00:00 NaN NaT Dec 2025-08-01 Integrated Telecommunication Services Telecommunication Services Integrated Telecommunication Services None Communication Services Telecommunication Services Diversified Telecommunication Services Bandung PT Danantara Asset Management (Persero) None None 0.520900 Jakarta Construction and Engineering Indonesia None None Jakarta Diversified Support Services Indonesia Indonesia (Prior Subsidiary or Operating Unit,... 21.060000 20859696180.000000 77180875.866000 0.090000 85029954.000000 314610.829800 NaT NaN None None None 47258399.000000 91256250.000000 0.542000 0.420000 -12375841.000000 14622310.000000 26998151.000000 511950.000000 1875773.000000 11444575.000000 46000.000000 NaN 17584731.000000 43997851.000000 71066244.000000 22159917.000000 64166429.000000 23225173.000000 35538389.000000 23225173.000000 14725429.000000 24553925.000000 -3250846.000000 98347123896.000000 135790.870326 1380.000000 61582582.000000 7156989.000000 NaN 2008
1 PT Abadi Nusantara Hijau Investama Tbk (IDX:PACK) 109420637 IDX IDX:PACK Packaging and Materials: Paper and Plastic Materials Indonesia Jl. Jababeka 2 Block C/11-D www.flexypack.com PT Abadi Nusantara Hijau Investama Tbk provide... Public Company None Operating None No None None Yes No No PT Abadi Nusantara Hijau Investama Tbk provide... Packaging Services None 2019.000000 2019-11-18 00:00:00 NaN NaT Dec NaT Packaging and Materials: Paper and Plastic Packaging and Materials: Paper and Plastic None None Materials Materials Containers and Packaging Bekasi None None None NA% None None None None None None None None PT Benson Kapital Indonesia (Current Investmen... 23.240000 371188000.000000 965088.800000 0.000000 0.000000 NaN 2023-02-06 162.000000 None None None NaN NaN None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2008
2 PT ABM Investama Tbk (IDX:ABMM) 4980353 IDX IDX:ABMM Coal and Consumable Fuels Energy and Utilities Indonesia Tiara Marga Trakindo I building www.abm-investama.com PT ABM Investama Tbk, together with its subsid... Public Company None Operating Subsidiary None No None None Yes No No PT ABM Investama Tbk operating in the sectors ... Clean Energy; Freight Service; Marine Transpor... Energy, Precious Metals 2006.000000 2006-06-01 00:00:00 NaN NaT Dec 2025-09-01 Coal and Consumable Fuels Oil, Gas and Coal Coal and Consumable Fuels None Energy Energy Oil, Gas and Consumable Fuels Jakarta PT Tiara Marga Trakindo None None 0.535500 Jakarta Industrial Conglomerates PT Tiara Marga Trakindo None None Jakarta Industrial Conglomerates Indonesia PT Tiara Marga Trakindo (Current Subsidiary or... 0.830000 22926501.000000 68779.503000 7.340000 202136500.000000 606409.500000 2011-12-05 3750.000000 None None None 4360950.000000 3549129.000000 0.592000 0.430000 -953832.000000 1384649.000000 2338481.000000 241667.000000 NaN NaN NaN NaN 0.000000 -811821.000000 1469802.000000 3114382.000000 3235172.000000 -469841.000000 NaN -469841.000000 -3913.000000 -4564.000000 7861.000000 NaN NaN NaN -811821.000000 159424.000000 NaN 2008
3 PT Ace Oldfields Tbk (IDX:KUAS) 7649048 IDX IDX:KUAS Building Products Industrials Indonesia Jl. Raya Cileungsi Jonggol www.aceoldfields.com PT Ace Oldfields Tbk manufactures and sells pa... Public Company None Operating None No None None Yes No No None None None 1989.000000 1989-09-18 00:00:00 NaN NaT Dec 2025-09-01 Building Products; Hardware Tools and Equipmen... Capital Goods Building Products None Industrials Capital Goods Building Products Bogor None None None NA% None None None None None None None None Oldfields Holdings Limited (Prior Investment, ... NaN NaN NaN 0.870000 11225400.000000 763.327200 2021-10-25 195.000000 None None None NaN NaN None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2008
4 PT Acset Indonusa Tbk (IDX:ACST) 4990054 IDX IDX:ACST Construction and Engineering Industrials Indonesia ACSET Building www.acset.co PT Acset Indonusa Tbk provides construction an... Public Company None Operating Subsidiary None No None None Yes No No PT Acset Indonusa Tbk, an integrated construct... None Civil Engineering, Construction 1995.000000 1995-01-10 00:00:00 1995.000000 1995-01-10 Dec 2025-09-01 Construction and Engineering; Commercial Const... Capital Goods Construction and Engineering None Industrials Capital Goods Construction and Engineering Jakarta PT Karya Supra Perkasa None None 0.501000 None Unclassified Jardine Matheson Holdings Limited J36 SGX:J36 Hamilton Industrial Conglomerates Bermuda PT Karya Supra Perkasa (Current Subsidiary or ... 0.110000 20245000.000000 2753.320000 NaN NaN NaN 2013-06-20 2500.000000 None None None NaN NaN None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2008
Shape df_final: (15708, 91)
# Buang baris yang mungkin masih ada NaN di exchange_ticker atau Year
df_final = df_final[
    df_final["exchange_ticker"].notna() &
    df_final["Year"].notna()
].copy()

print("=== df_final setelah filter ticker & Year non-NaN ===")
display(df_final.head(20))

print("Total baris:", len(df_final))
print("Jumlah emiten unik:", df_final["exchange_ticker"].nunique())
print("Jumlah Year unik:", df_final["Year"].nunique())
=== df_final setelah filter ticker & Year non-NaN ===
variable Entity Name Entity ID Exchange exchange_ticker Primary Industry 1st Level Primary Industry Country / Region Name Address 1 Web Address Business Description Company Type Investor Type Company Status Ownership Structure Activist Investor? Yes/No Institution Coverage Level Consolidation Status Has Current Financials? Yes/No Has Bank Regulatory Financials? Yes/No Has Third Party Financials? Yes/No Long Business Description Topic Tags Crunchbase Categories Year Established Date Established MM/dd/yyyy Year Incorporated Date Incorporated MM/dd/yyyy Month of Fiscal Year End Headcount As Of MM/dd/yyyy Industry Classification 2nd Level Primary Industry 3rd Level Primary Industry 4th Level Primary Industry Sector Industry Group Industry City Parent Company Name Parent Ticker Parent Exchange: Ticker Parent Percent Owned (%) Parent City Parent Industry Ultimate Parent Company Name Ultimate Parent Ticker Ultimate Parent Exchange: Ticker Ultimate Parent City Ultimate Parent Industry Ultimate Parent Country / Region All Investors Percent Owned - All Institutions (%) Shares Owned - All Institutions (actual) Market Value - All Institutions (Rp.B) Percent Owned - Insiders (%) Shares Owned - Insiders (actual) Market Value - Insiders (Rp.B) IPO Date MM/dd/yyyy IPO Price (Rp.) Location Type Data Precision Data Year Total Liabilities (Rp.M) Total Assets (Rp.M) Current Ratio (x) Quick Ratio (x) Working Capital (Rp.M) Total Current Assets (Rp.M) Total Current Liabilities (Rp.M) Inventory (Rp.M) Prepaid Exp. (Rp.M) Long-term Debt (Rp.M) Short-term Borrowings (Rp.M) Current Portion of LT Debt & Leases (Rp.M) Total Debt (Rp.M) Total Equity (Rp.M) Net Property, Plant & Equipment (Rp.M) Cost Of Goods Sold (Rp.M) Total Revenue (Rp.M) Operating Income (Rp.M) EBITDA (Rp.M) EBIT (Rp.M) Net Income to Company (Rp.M) Cash from Ops. (Rp.M) Net Change in Cash (Rp.M) ECS Total Common Shares Outstanding (actual) Market Capitalization (Rp.B) Day Close Price (Rp.) Total Capital (Rp.M) Cash & Short-term Investments (Rp.M) Net Intangibles (Rp.M) Year
0 Perusahaan Perseroan (Persero) PT Telekomunika... 4210975 IDX IDX:TLKM Integrated Telecommunication Services Technology, Media & Telecommunications Indonesia Jl. Japati No. 1 www.telkom.co.id Perusahaan Perseroan (Persero) PT Telekomunika... Public Company None Operating Subsidiary None No None None Yes No No Perusahaan Perseroan (Persero) PT Telekomunika... Network Backbone; Optical Infrastructure; Clou... Digital Media, Information Technology, Telecom... 1884.000000 27/03/1884 01:00:00 NaN NaT Dec 2025-08-01 Integrated Telecommunication Services Telecommunication Services Integrated Telecommunication Services None Communication Services Telecommunication Services Diversified Telecommunication Services Bandung PT Danantara Asset Management (Persero) None None 0.520900 Jakarta Construction and Engineering Indonesia None None Jakarta Diversified Support Services Indonesia Indonesia (Prior Subsidiary or Operating Unit,... 21.060000 20859696180.000000 77180875.866000 0.090000 85029954.000000 314610.829800 NaT NaN None None None 47258399.000000 91256250.000000 0.542000 0.420000 -12375841.000000 14622310.000000 26998151.000000 511950.000000 1875773.000000 11444575.000000 46000.000000 NaN 17584731.000000 43997851.000000 71066244.000000 22159917.000000 64166429.000000 23225173.000000 35538389.000000 23225173.000000 14725429.000000 24553925.000000 -3250846.000000 98347123896.000000 135790.870326 1380.000000 61582582.000000 7156989.000000 NaN 2008
1 PT Abadi Nusantara Hijau Investama Tbk (IDX:PACK) 109420637 IDX IDX:PACK Packaging and Materials: Paper and Plastic Materials Indonesia Jl. Jababeka 2 Block C/11-D www.flexypack.com PT Abadi Nusantara Hijau Investama Tbk provide... Public Company None Operating None No None None Yes No No PT Abadi Nusantara Hijau Investama Tbk provide... Packaging Services None 2019.000000 2019-11-18 00:00:00 NaN NaT Dec NaT Packaging and Materials: Paper and Plastic Packaging and Materials: Paper and Plastic None None Materials Materials Containers and Packaging Bekasi None None None NA% None None None None None None None None PT Benson Kapital Indonesia (Current Investmen... 23.240000 371188000.000000 965088.800000 0.000000 0.000000 NaN 2023-02-06 162.000000 None None None NaN NaN None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2008
2 PT ABM Investama Tbk (IDX:ABMM) 4980353 IDX IDX:ABMM Coal and Consumable Fuels Energy and Utilities Indonesia Tiara Marga Trakindo I building www.abm-investama.com PT ABM Investama Tbk, together with its subsid... Public Company None Operating Subsidiary None No None None Yes No No PT ABM Investama Tbk operating in the sectors ... Clean Energy; Freight Service; Marine Transpor... Energy, Precious Metals 2006.000000 2006-06-01 00:00:00 NaN NaT Dec 2025-09-01 Coal and Consumable Fuels Oil, Gas and Coal Coal and Consumable Fuels None Energy Energy Oil, Gas and Consumable Fuels Jakarta PT Tiara Marga Trakindo None None 0.535500 Jakarta Industrial Conglomerates PT Tiara Marga Trakindo None None Jakarta Industrial Conglomerates Indonesia PT Tiara Marga Trakindo (Current Subsidiary or... 0.830000 22926501.000000 68779.503000 7.340000 202136500.000000 606409.500000 2011-12-05 3750.000000 None None None 4360950.000000 3549129.000000 0.592000 0.430000 -953832.000000 1384649.000000 2338481.000000 241667.000000 NaN NaN NaN NaN 0.000000 -811821.000000 1469802.000000 3114382.000000 3235172.000000 -469841.000000 NaN -469841.000000 -3913.000000 -4564.000000 7861.000000 NaN NaN NaN -811821.000000 159424.000000 NaN 2008
3 PT Ace Oldfields Tbk (IDX:KUAS) 7649048 IDX IDX:KUAS Building Products Industrials Indonesia Jl. Raya Cileungsi Jonggol www.aceoldfields.com PT Ace Oldfields Tbk manufactures and sells pa... Public Company None Operating None No None None Yes No No None None None 1989.000000 1989-09-18 00:00:00 NaN NaT Dec 2025-09-01 Building Products; Hardware Tools and Equipmen... Capital Goods Building Products None Industrials Capital Goods Building Products Bogor None None None NA% None None None None None None None None Oldfields Holdings Limited (Prior Investment, ... NaN NaN NaN 0.870000 11225400.000000 763.327200 2021-10-25 195.000000 None None None NaN NaN None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2008
4 PT Acset Indonusa Tbk (IDX:ACST) 4990054 IDX IDX:ACST Construction and Engineering Industrials Indonesia ACSET Building www.acset.co PT Acset Indonusa Tbk provides construction an... Public Company None Operating Subsidiary None No None None Yes No No PT Acset Indonusa Tbk, an integrated construct... None Civil Engineering, Construction 1995.000000 1995-01-10 00:00:00 1995.000000 1995-01-10 Dec 2025-09-01 Construction and Engineering; Commercial Const... Capital Goods Construction and Engineering None Industrials Capital Goods Construction and Engineering Jakarta PT Karya Supra Perkasa None None 0.501000 None Unclassified Jardine Matheson Holdings Limited J36 SGX:J36 Hamilton Industrial Conglomerates Bermuda PT Karya Supra Perkasa (Current Subsidiary or ... 0.110000 20245000.000000 2753.320000 NaN NaN NaN 2013-06-20 2500.000000 None None None NaN NaN None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2008
5 PT Adaro Andalan Indonesia Tbk (IDX:AADI) 4422654 IDX IDX:AADI Coal and Consumable Fuels Energy and Utilities Indonesia Cyber 2 Tower www.adaroindonesia.com PT Adaro Andalan Indonesia Tbk engages in the ... Public Company None Operating None No None None Yes No No None Freight Service; Marine Transportation; Mining... None 2004.000000 2004-12-01 00:00:00 NaN NaT None 2025-09-01 Coal and Consumable Fuels Oil, Gas and Coal Coal and Consumable Fuels None Energy Energy Oil, Gas and Consumable Fuels Jakarta None None None NA% None None None None None None None None PT Alamtri Resources Indonesia Tbk (Current In... 1.890000 147513763.000000 1143231.663250 5.870000 456756180.000000 3539860.395000 2024-12-03 5550.000000 None None None NaN NaN None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2008
6 PT Adhi Commuter Properti Tbk (IDX:ADCP) 13992197 IDX IDX:ADCP Real Estate Development Real Estate Indonesia Jalan Pengantin Ali No. 88 Ciracas www.adcp.co.id PT Adhi Commuter Properti Tbk operates as a ma... Public Company None Operating Subsidiary None No None None Yes No No PT Adhi Commuter Properti Tbk operates in the ... Smart Building; Real Estate Technology Property Management, Real Estate 2018.000000 2018-03-09 00:00:00 NaN NaT Dec 2025-09-01 Real Estate Development Real Estate Management and Development Real Estate Development None Real Estate Real Estate Management and Development Real Estate Management and Development Jakarta Timur PT Adhi Karya (Persero) Tbk ADHI IDX:ADHI NA% Jakarta Construction and Engineering Indonesia None None Jakarta Diversified Support Services Indonesia PT Adhi Karya (Persero) Tbk (Current Subsidiar... NaN NaN NaN 0.000000 330700.000000 17.857800 2022-02-21 130.000000 None None None NaN NaN None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2008
7 PT Adhi Kartiko Pratama Tbk (IDX:NICE) 114680692 IDX IDX:NICE Diversified Metals and Mining Materials Indonesia Jalan Sorumba Nomor 80 www.akp.co.id PT Adhi Kartiko Pratama Tbk operates as a nick... Public Company None Operating Subsidiary Stock Corporation No Full Consolidated Yes No No PT Adhi Kartiko Pratama Tbk operates as a nick... Marine Transportation; Mining Technology Mining 2008.000000 2008-07-09 00:00:00 2008.000000 2008-07-09 Dec 2025-09-01 Diversified Metals and Mining; Diversified Met... Metals and Mining Diversified Metals and Mining None Materials Materials Metals and Mining Kendari PT. Energy Battery Indonesia None None 0.600000 Jakarta Selatan Unclassified LX International Corp. A001120 KOSE:A001120 Seoul Trading Companies and Distributors South Korea PT Indo Tambangraya Megah Tbk (Current Investm... 0.150000 9382800.000000 3828.182400 NaN NaN NaN 2024-01-05 438.000000 None None None NaN NaN None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2008
8 PT Adhi Karya (Persero) Tbk (IDX:ADHI) 4995632 IDX IDX:ADHI Construction and Engineering Industrials Indonesia Jalan Raya Pasar Minggu Km. 18 adhi.co.id PT Adhi Karya (Persero) Tbk engages in the con... Public Company None Operating None No None None Yes No No None Environmental Engineering Construction,Infrastructure,Real Estate 1960.000000 1960-03-11 00:00:00 NaN NaT Dec 2025-09-01 Construction and Engineering; Commercial Const... Capital Goods Construction and Engineering None Industrials Capital Goods Construction and Engineering Jakarta Indonesia None None NA% Jakarta Diversified Support Services Indonesia None None Jakarta Diversified Support Services Indonesia Indonesia (Current Investment, Majority) 0.260000 21597230.000000 5528.890880 0.060000 5119600.000000 1310.617600 NaT NaN None None None 4525468.985000 5125368.542000 1.174000 0.856000 689925.514000 4652976.411000 3963050.897000 606987.785000 15869.334000 498313.341000 217249.869000 NaN 820626.327000 599899.556000 247790.039000 6095668.547000 6639941.611000 192704.468000 223441.322000 192704.468000 82994.360000 -3306.168000 -419393.749000 1769847500.000000 486.356400 270.000000 1420525.883000 365762.580000 NaN 2008
9 PT Adi Sarana Armada Tbk (IDX:ASSA) 4994188 IDX IDX:ASSA Passenger Ground Transportation Industrials Indonesia Graha Kirana Building www.assarent.co.id PT Adi Sarana Armada Tbk provides corporate ca... Public Company None Operating None No None None Yes No No None Fleet Management; Freight Service; Car Sharing Logistics,Railroad,Transportation 1999.000000 1999-12-17 00:00:00 NaN NaT Dec 2025-09-01 Passenger Ground Transportation; Motor Vehicle... Transportation Passenger Ground Transportation None Industrials Transportation Ground Transportation Jakarta Utara None None None NA% None None None None None None None None PT Triputra Investindo Arya (Prior Investment,... 8.180000 302116753.000000 332328.428300 23.460000 866110656.000000 952721.721600 2012-11-08 390.000000 None None None NaN NaN None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2008
10 PT Adira Dinamika Multi Finance Tbk (IDX:ADMF) 4310851 IDX IDX:ADMF Specialty Finance: Consumer Focused Financials Indonesia Millennium Centennial Center adira.co.id PT Adira Dinamika Multi Finance Tbk provides c... Public Company None Operating Subsidiary Stock Corporation No Summary Consolidated Yes No No PT Adira Dinamika Multi Finance Tbk provides c... Consumer Lending; Micro Lending Financial Services 1990.000000 1990-11-13 00:00:00 NaN NaT Dec 2025-09-01 Specialty Finance: Consumer Focused Specialty Finance Specialty Finance: Consumer Focused None Financials Financial Services Consumer Finance Jakarta Selatan PT Bank Danamon Indonesia Tbk BDMN IDX:BDMN 0.920700 Jakarta Selatan Bank Mitsubishi UFJ Financial Group, Inc. 8306 TSE:8306 Tokyo Bank Japan MUFG Bank, Ltd. (Prior Subsidiary or Operating... 0.440000 4366312.000000 38423.545600 NaN NaN NaN NaT NaN None None None 1642021.000000 3592024.000000 2.395000 2.278000 1417431.000000 2433353.000000 1015922.000000 NaN NaN 254534.000000 NaN NaN 844876.000000 1950003.000000 155195.000000 749482.000000 2930949.000000 1406208.000000 NaN NaN 1020233.000000 -3207435.000000 97892.000000 1000000000.000000 1450.000000 1450.000000 2794879.000000 474195.000000 NaN 2008
11 PT Adiwarna Anugerah Abadi Tbk (IDX:NAIK) 12731053 IDX IDX:NAIK Office Services and Supplies Industrials Indonesia Perkantoran Mutiara Taman Palem No.53 www.adiwarna.co.id PT Adiwarna Anugerah Abadi Tbk designs, suppli... Public Company None Operating Subsidiary None No None None Yes No No None Environment, Health and Safety (EH&S) None 2007.000000 2007-05-08 00:00:00 NaN NaT None 2025-09-01 Office Services and Supplies Commercial and Professional Services Office Services and Supplies None Industrials Commercial and Professional Services Commercial Services and Supplies Jakarta Barat PT Adiwarna Anugerah Investama None None 0.725800 None Unclassified PT Adiwarna Anugerah Investama None None None Unclassified Indonesia PT Adiwarna Anugerah Investama (Current Subsid... 0.040000 1392000.000000 175.392000 20.610000 685616400.000000 86387.666400 2024-11-11 107.000000 None None None NaN NaN None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2008
12 PT Aesler Grup Internasional Tbk (IDX:RONY) 20023601 IDX IDX:RONY Construction and Engineering Industrials Indonesia Noble House Building None PT Aesler Grup Internasional Tbk provides arch... Public Company None Operating None No None None Yes No No PT Aesler Grup Internasional Tbk (PT Aesler) o... Sensors Architecture, Construction 2017.000000 2017-08-04 00:00:00 NaN NaT Dec NaT Construction and Engineering; Construction Sup... Capital Goods Construction and Engineering None Industrials Capital Goods Construction and Engineering Jakarta Selatan None None None NA% None None None None None None None None Honour Accord Limited (Pending Acquisition or ... NaN NaN NaN 0.000000 0.000000 NaN 2020-04-07 100.000000 None None None NaN NaN None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2008
13 PT Agro Bahari Nusantara Tbk (IDX:UDNG) 114111620 IDX IDX:UDNG Packaged Foods and Meats Producers Consumer Indonesia Ruko Shibuya SBC 012 PIK 2 abn.farm PT Agro Bahari Nusantara Tbk, together with it... Public Company None Operating None No None None Yes No No PT Agro Bahari Nusantara Tbk operates primaril... Aquaculture Aquaculture, Food Processing 2019.000000 2019-04-01 00:00:00 NaN NaT Dec 2025-09-01 Packaged Foods and Meats Producers; Seafood, S... Producers Packaged Foods and Meats Producers None Consumer Staples Food, Beverage and Tobacco Food Products Tangerang None None None NA% None None None None None None None None PT Adrina Abdi Wisesa (Current Investment, Min... NaN NaN NaN 71.430000 1250000000.000000 4337500.000000 2023-10-27 100.000000 None None None NaN NaN None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2008
14 PT Agro Yasa Lestari Tbk (IDX:AYLS) 12884080 IDX IDX:AYLS Trading Companies and Distributors Industrials Indonesia Gondangdia Lama Building 25 agroyasalestari.com PT Agro Yasa Lestari Tbk designs, supplies, an... Public Company None Operating Subsidiary None No None None Yes No No None None Civil Engineering, Construction 2010.000000 2010-02-15 00:00:00 NaN NaT Dec NaT Trading Companies and Distributors; Constructi... Capital Goods Trading Companies and Distributors None Industrials Capital Goods Trading Companies and Distributors Jakarta Pusat PT Anugrah Cakrawala Dunia None None 0.696900 Grogol Petamburan Trading Companies and Distributors PT Anugrah Cakrawala Dunia None None Grogol Petamburan Trading Companies and Distributors Indonesia PT Anugrah Cakrawala Dunia (Current Subsidiary... NaN NaN NaN NaN NaN NaN 2020-02-07 100.000000 None None None NaN NaN None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2008
15 PT Agung Menjangan Mas Tbk (IDX:AMMS) 106546520 IDX IDX:AMMS Packaged Foods and Meats Producers Consumer Indonesia Generali Tower Gran Rubina Business Park agungmm.com PT Agung Menjangan Mas Tbk provides services t... Public Company None Operating Subsidiary None No None None Yes No No PT Agung Menjangan Mas Tbk engages in the agri... Aquaculture None 2004.000000 None NaN NaT Dec NaT Packaged Foods and Meats Producers; Seafood, S... Producers Packaged Foods and Meats Producers None Consumer Staples Food, Beverage and Tobacco Food Products Jakarta PT Mandara Mas Semesta None None 0.997700 None Unclassified PT Mandara Mas Semesta None None None Unclassified Indonesia PT Mandara Mas Semesta (Current Subsidiary or ... NaN NaN NaN 2.540000 30437500.000000 14549.125000 2022-08-04 100.000000 None None None NaN NaN None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2008
16 PT Agung Podomoro Land Tbk (IDX:APLN) 4432432 IDX IDX:APLN Real Estate Development Real Estate Indonesia APL Tower www.agungpodomoroland.com PT Agung Podomoro Land Tbk, together with its ... Public Company None Operating Subsidiary Stock Corporation No Full Consolidated Yes No No PT Agung Podomoro Land Tbk engages in property... None Commercial, Property Development, Real Estate,... 2004.000000 2004-07-30 00:00:00 NaN NaT Dec 2025-08-01 Real Estate Development Real Estate Management and Development Real Estate Development None Real Estate Real Estate Management and Development Real Estate Management and Development Jakarta PT Indofica None None 0.827200 Jakarta Real Estate Development PT Indofica None None Jakarta Real Estate Development Indonesia PT Indofica (Current Subsidiary or Operating U... 0.020000 3760100.000000 368.489800 5.020000 1139706185.000000 111691.206130 2010-10-28 365.000000 None None None 2032513.571000 2734460.554000 5.148000 1.978000 1088876.420000 1351411.625000 262535.205000 803761.971000 2972.490000 995393.484000 NaN NaN 1070393.484000 701946.983000 1229306.782000 686364.623000 810031.342000 -50087.790000 -48343.054000 -50087.790000 -25050.220000 734367.660000 280429.191000 150000000.000000 NaN NaN 1772340.467000 340551.102000 NaN 2008
17 PT Agung Semesta Sejahtera Tbk (IDX:TARA) 4990209 IDX IDX:TARA Construction and Engineering Industrials Indonesia Wisma 77 Lt. 19 www.agungsemestasejahtera.com PT Agung Semesta Sejahtera Tbk primarily engag... Public Company None Operating None No None None Yes No No PT Agung Semesta Sejahtera Tbk engages in dive... None Property Development, Property Management, Rea... 2006.000000 2006-06-01 00:00:00 NaN NaT Dec NaT Construction and Engineering; Commercial Const... Capital Goods Construction and Engineering None Industrials Capital Goods Construction and Engineering Jakarta Barat None None None NA% None None None None None None None None None 5.480000 551389038.000000 14336.114988 NaN NaN NaN 2014-07-08 106.000000 None None None NaN NaN None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2008
18 PT AirAsia Indonesia Tbk (IDX:CMPP) 7476312 IDX IDX:CMPP Passenger Airlines Industrials Indonesia AirAsia Redhouse ir.aaid.co.id PT AirAsia Indonesia Tbk provides scheduled co... Public Company None Operating None No None None Yes No No PT Airasia Indonesia Tbk is a prominent airlin... None Air Transportation, Transportation 1989.000000 1989-07-25 00:00:00 NaN NaT Dec NaT Passenger Airlines; Commercial Airlines Transportation Passenger Airlines None Industrials Transportation Passenger Airlines Tangerang None None None NA% None None None None None None None None Capital A Berhad (Prior Subsidiary or Operatin... NaN NaN NaN NaN NaN NaN NaT NaN None None None NaN NaN None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2008
19 PT Akasha Wira International Tbk (IDX:ADES) 4913827 IDX IDX:ADES Soft Drinks and Non-alcoholic Beverages Consumer Indonesia Jl. TB. Simatupang Kav. 89 RT 01 RW 02 www.akashainternational.com PT Akasha Wira International Tbk engages in th... Public Company None Operating Subsidiary None No None None Yes No No PT Akasha Wira International Tbk engages in th... Lifestyle; Nutraceutical; Alternative Protein Consumer Goods, Manufacturing 1985.000000 None 2006.000000 2006-03-10 Dec 2025-09-01 Soft Drinks and Non-alcoholic Beverages; Bottl... Producers Beverage Producers Soft Drinks and Non-alcoholic Beverages Consumer Staples Food, Beverage and Tobacco Beverages Jakarta Selatan Water Partners Bottling S.A. None None 0.915200 Zurich Unclassified Water Partners Bottling S.A. None None Zurich Unclassified Switzerland Nestlé S.A. (Prior Investment, Minority); Wate... 0.010000 62100.000000 954.787500 0.000000 11900.000000 182.962500 NaT NaN None None None 133117.000000 185015.000000 0.514000 0.394000 -56009.000000 59208.000000 115217.000000 9581.000000 993.000000 NaN 84251.000000 NaN 84251.000000 51898.000000 124311.000000 94734.000000 129542.000000 -38740.000000 -21976.000000 -38740.000000 -15208.000000 -48514.000000 25286.000000 589896800.000000 132.726780 225.000000 136149.000000 29311.000000 NaN 2008
Total baris: 15708
Jumlah emiten unik: 924
Jumlah Year unik: 17
# Tentukan beberapa kolom penting di depan
kolom_depan = [
    "exchange_ticker",
    "Year",
    "Entity Name",
    "IPO Date MM/dd/yyyy",
    "Primary Industry",
]

# Sisanya biarkan mengikuti urutan semula
kolom_lain = [c for c in df_final.columns if c not in kolom_depan]

df_findat_final = df_final[kolom_depan + kolom_lain]

print("=== df_final dengan urutan kolom yang rapi ===")
df_findat_final
=== df_final dengan urutan kolom yang rapi ===
variable exchange_ticker Year Entity Name IPO Date MM/dd/yyyy Primary Industry Entity ID Exchange 1st Level Primary Industry Country / Region Name Address 1 Web Address Business Description Company Type Investor Type Company Status Ownership Structure Activist Investor? Yes/No Institution Coverage Level Consolidation Status Has Current Financials? Yes/No Has Bank Regulatory Financials? Yes/No Has Third Party Financials? Yes/No Long Business Description Topic Tags Crunchbase Categories Year Established Date Established MM/dd/yyyy Year Incorporated Date Incorporated MM/dd/yyyy Month of Fiscal Year End Headcount As Of MM/dd/yyyy Industry Classification 2nd Level Primary Industry 3rd Level Primary Industry 4th Level Primary Industry Sector Industry Group Industry City Parent Company Name Parent Ticker Parent Exchange: Ticker Parent Percent Owned (%) Parent City Parent Industry Ultimate Parent Company Name Ultimate Parent Ticker Ultimate Parent Exchange: Ticker Ultimate Parent City Ultimate Parent Industry Ultimate Parent Country / Region All Investors Percent Owned - All Institutions (%) Shares Owned - All Institutions (actual) Market Value - All Institutions (Rp.B) Percent Owned - Insiders (%) Shares Owned - Insiders (actual) Market Value - Insiders (Rp.B) IPO Price (Rp.) Location Type Data Precision Data Year Total Liabilities (Rp.M) Total Assets (Rp.M) Current Ratio (x) Quick Ratio (x) Working Capital (Rp.M) Total Current Assets (Rp.M) Total Current Liabilities (Rp.M) Inventory (Rp.M) Prepaid Exp. (Rp.M) Long-term Debt (Rp.M) Short-term Borrowings (Rp.M) Current Portion of LT Debt & Leases (Rp.M) Total Debt (Rp.M) Total Equity (Rp.M) Net Property, Plant & Equipment (Rp.M) Cost Of Goods Sold (Rp.M) Total Revenue (Rp.M) Operating Income (Rp.M) EBITDA (Rp.M) EBIT (Rp.M) Net Income to Company (Rp.M) Cash from Ops. (Rp.M) Net Change in Cash (Rp.M) ECS Total Common Shares Outstanding (actual) Market Capitalization (Rp.B) Day Close Price (Rp.) Total Capital (Rp.M) Cash & Short-term Investments (Rp.M) Net Intangibles (Rp.M)
0 IDX:TLKM 2008 Perusahaan Perseroan (Persero) PT Telekomunika... NaT Integrated Telecommunication Services 4210975 IDX Technology, Media & Telecommunications Indonesia Jl. Japati No. 1 www.telkom.co.id Perusahaan Perseroan (Persero) PT Telekomunika... Public Company None Operating Subsidiary None No None None Yes No No Perusahaan Perseroan (Persero) PT Telekomunika... Network Backbone; Optical Infrastructure; Clou... Digital Media, Information Technology, Telecom... 1884.000000 27/03/1884 01:00:00 NaN NaT Dec 2025-08-01 Integrated Telecommunication Services Telecommunication Services Integrated Telecommunication Services None Communication Services Telecommunication Services Diversified Telecommunication Services Bandung PT Danantara Asset Management (Persero) None None 0.520900 Jakarta Construction and Engineering Indonesia None None Jakarta Diversified Support Services Indonesia Indonesia (Prior Subsidiary or Operating Unit,... 21.060000 20859696180.000000 77180875.866000 0.090000 85029954.000000 314610.829800 NaN None None None 47258399.000000 91256250.000000 0.542000 0.420000 -12375841.000000 14622310.000000 26998151.000000 511950.000000 1875773.000000 11444575.000000 46000.000000 NaN 17584731.000000 43997851.000000 71066244.000000 22159917.000000 64166429.000000 23225173.000000 35538389.000000 23225173.000000 14725429.000000 24553925.000000 -3250846.000000 98347123896.000000 135790.870326 1380.000000 61582582.000000 7156989.000000 NaN
1 IDX:PACK 2008 PT Abadi Nusantara Hijau Investama Tbk (IDX:PACK) 2023-02-06 Packaging and Materials: Paper and Plastic 109420637 IDX Materials Indonesia Jl. Jababeka 2 Block C/11-D www.flexypack.com PT Abadi Nusantara Hijau Investama Tbk provide... Public Company None Operating None No None None Yes No No PT Abadi Nusantara Hijau Investama Tbk provide... Packaging Services None 2019.000000 2019-11-18 00:00:00 NaN NaT Dec NaT Packaging and Materials: Paper and Plastic Packaging and Materials: Paper and Plastic None None Materials Materials Containers and Packaging Bekasi None None None NA% None None None None None None None None PT Benson Kapital Indonesia (Current Investmen... 23.240000 371188000.000000 965088.800000 0.000000 0.000000 NaN 162.000000 None None None NaN NaN None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
2 IDX:ABMM 2008 PT ABM Investama Tbk (IDX:ABMM) 2011-12-05 Coal and Consumable Fuels 4980353 IDX Energy and Utilities Indonesia Tiara Marga Trakindo I building www.abm-investama.com PT ABM Investama Tbk, together with its subsid... Public Company None Operating Subsidiary None No None None Yes No No PT ABM Investama Tbk operating in the sectors ... Clean Energy; Freight Service; Marine Transpor... Energy, Precious Metals 2006.000000 2006-06-01 00:00:00 NaN NaT Dec 2025-09-01 Coal and Consumable Fuels Oil, Gas and Coal Coal and Consumable Fuels None Energy Energy Oil, Gas and Consumable Fuels Jakarta PT Tiara Marga Trakindo None None 0.535500 Jakarta Industrial Conglomerates PT Tiara Marga Trakindo None None Jakarta Industrial Conglomerates Indonesia PT Tiara Marga Trakindo (Current Subsidiary or... 0.830000 22926501.000000 68779.503000 7.340000 202136500.000000 606409.500000 3750.000000 None None None 4360950.000000 3549129.000000 0.592000 0.430000 -953832.000000 1384649.000000 2338481.000000 241667.000000 NaN NaN NaN NaN 0.000000 -811821.000000 1469802.000000 3114382.000000 3235172.000000 -469841.000000 NaN -469841.000000 -3913.000000 -4564.000000 7861.000000 NaN NaN NaN -811821.000000 159424.000000 NaN
3 IDX:KUAS 2008 PT Ace Oldfields Tbk (IDX:KUAS) 2021-10-25 Building Products 7649048 IDX Industrials Indonesia Jl. Raya Cileungsi Jonggol www.aceoldfields.com PT Ace Oldfields Tbk manufactures and sells pa... Public Company None Operating None No None None Yes No No None None None 1989.000000 1989-09-18 00:00:00 NaN NaT Dec 2025-09-01 Building Products; Hardware Tools and Equipmen... Capital Goods Building Products None Industrials Capital Goods Building Products Bogor None None None NA% None None None None None None None None Oldfields Holdings Limited (Prior Investment, ... NaN NaN NaN 0.870000 11225400.000000 763.327200 195.000000 None None None NaN NaN None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
4 IDX:ACST 2008 PT Acset Indonusa Tbk (IDX:ACST) 2013-06-20 Construction and Engineering 4990054 IDX Industrials Indonesia ACSET Building www.acset.co PT Acset Indonusa Tbk provides construction an... Public Company None Operating Subsidiary None No None None Yes No No PT Acset Indonusa Tbk, an integrated construct... None Civil Engineering, Construction 1995.000000 1995-01-10 00:00:00 1995.000000 1995-01-10 00:00:00 Dec 2025-09-01 Construction and Engineering; Commercial Const... Capital Goods Construction and Engineering None Industrials Capital Goods Construction and Engineering Jakarta PT Karya Supra Perkasa None None 0.501000 None Unclassified Jardine Matheson Holdings Limited J36 SGX:J36 Hamilton Industrial Conglomerates Bermuda PT Karya Supra Perkasa (Current Subsidiary or ... 0.110000 20245000.000000 2753.320000 NaN NaN NaN 2500.000000 None None None NaN NaN None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
15703 IDX:ADMG 2024 PT. Polychem Indonesia Tbk (IDX:ADMG) NaT Commodity Chemicals 4986761 IDX Materials Indonesia Gedung Wisma 46 Kota BNI www.polychemindo.com PT. Polychem Indonesia Tbk engages in the chem... Public Company None Operating None No None None Yes No No None Bio-based and Renewable Materials Chemical, Manufacturing 1978.000000 None NaN NaT Dec 2025-08-01 Commodity Chemicals; Industrial Organic Chemic... Chemicals Commodity Chemicals None Materials Materials Chemicals Jakarta Pusat Provestment Limited None None 0.495000 None Unclassified Provestment Limited None None None Unclassified Indonesia HSBC Global Asset Management (UK) Limited (Pri... NaN NaN NaN 6.700000 260689900.000000 42231.763800 NaN None None None 633525.279930 2502397.042404 1.582000 0.658000 324806.222988 883354.230822 558548.007834 457628.197014 9209.520918 NaN NaN NaN 2306.714616 1868871.762474 1550937.131766 1721786.548931 1738143.281730 -64988.560197 8869.037256 -64988.560197 -161541.845097 29243.074738 23742.525826 3889179559.000000 423.920572 109.000000 1871178.477090 204124.233702 NaN
15704 IDX:BIMA 2024 PT. Primarindo Asia Infrastructure, Tbk. (IDX:... NaT Footwear Producers 4914053 IDX Consumer Indonesia Gedung Tatapuri www.primarindo.co.id PT. Primarindo Asia Infrastructure, Tbk. engag... Public Company None Operating Subsidiary None No None None Yes No No PT. Primarindo Asia Infrastructure, Tbk. engag... Lifestyle Shoes 1988.000000 1988-07-01 00:00:00 NaN NaT Dec 2025-09-01 Footwear Producers Producers Apparel, Footwear and Textile Producers Footwear Producers Consumer Discretionary Consumer Durables and Apparel Textiles, Apparel and Luxury Goods Jakarta PT. Golden Lestari None None 0.865600 None Footwear Producers PT. Golden Lestari None None None Footwear Producers Indonesia PT. Golden Lestari (Current Subsidiary or Oper... NaN NaN NaN NaN NaN NaN NaN None None None 254016.336000 319791.244000 0.486000 0.057000 -65139.176000 61640.844000 126780.020000 53945.441000 151.607000 NaN 38535.590000 NaN 39348.507000 65774.907000 144368.933000 63473.693000 92500.319000 -7837.238000 -7539.673000 -7837.238000 -14713.779000 576.555000 574.851000 608175716.000000 52.911287 87.000000 105123.414000 1132.703000 2149.983000
15705 IDX:ARTI 2024 PT. Ratu Prabu Energi, Tbk (IDX:ARTI) NaT Oil and Gas Equipment and Services 4978413 IDX Energy and Utilities Indonesia Gedung Ratu Prabu 1 www.ratuprabuenergi.com PT. Ratu Prabu Energi, Tbk, through its subsid... Public Company None Operating None No None None Yes No No PT. Ratu Prabu Energi Tbk, through its subsidi... Mining Technology Energy, Oil and Gas 1993.000000 1993-03-31 01:00:00 1993.000000 1993-03-31 01:00:00 Dec NaT Oil and Gas Equipment and Services; Oil and Ga... Oil, Gas and Coal Oil and Gas Equipment and Services None Energy Energy Energy Equipment and Services Jakarta Selatan None None None NA% None None None None None None None None None NaN NaN NaN 1.830000 143195600.000000 286.391200 NaN None None None 753564.461000 550707.999000 0.002000 0.002000 -744152.585000 1449.639000 745602.224000 NaN NaN NaN NaN NaN 7447.628000 -202856.462000 80983.328000 24540.998000 37082.047000 1384.617000 19343.993000 1384.617000 -4425.791000 1400.707000 -530.714000 7840000000.000000 15.680000 2.000000 -195408.833000 464.548000 NaN
15706 IDX:TGRA 2024 PT. Terregra Asia Energy Tbk (IDX:TGRA) 2017-05-15 Renewable Electricity 6676356 IDX Energy and Utilities Indonesia Lippo Puri Tower www.terregra.co.id PT. Terregra Asia Energy Tbk develops, builds,... Public Company None Operating None No None None Yes No No PT. Terregra Asia Energy Tbk is a prominent pl... Clean Energy; CleanTech; Power Grid; Utility S... Energy, Renewable Energy 1995.000000 1995-11-07 00:00:00 NaN NaT Dec 2025-09-01 Renewable Electricity; Hydroelectric Power Gen... Renewable Electricity None None Utilities Utilities Independent Power and Renewable Electricity Pr... Jakarta Barat PT terregra asia equity None None 0.164600 None Unclassified PT terregra asia equity None None None Unclassified Indonesia Permodalan Nasional Berhad (Current Investment... 76.540000 2104808600.000000 56829.832200 5.970000 164141600.000000 4431.823200 200.000000 None None None 147870.135000 458585.037000 0.249000 0.017000 -41786.202000 13851.873000 55638.076000 NaN NaN 88291.188000 8816.043000 NaN 97107.231000 310714.902000 356206.187000 355.071000 NaN -13672.830000 -13091.231000 -13672.830000 -23698.806000 -4570.877000 -566.809000 2750000000.000000 85.250000 31.000000 407822.132000 181.181000 NaN
15707 IDX:FILM 2024 PT.MD Entertainment Tbk (IDX:FILM) 2018-08-02 Movies and Entertainment 10882429 IDX Technology, Media & Telecommunications Indonesia MD Place, Tower 1 www.mdentertainment.com PT.MD Entertainment Tbk produces, trades in, a... Public Company None Operating None No None None Yes No No PT.MD Entertainment Tbk produces, trades in, a... Digital Media; Content Creation Film Production, TV 2002.000000 2002-08-01 00:00:00 NaN NaT Dec 2025-09-01 Movies and Entertainment; Entertainment Produc... Media and Entertainment Movies and Entertainment None Communication Services Media and Entertainment Entertainment Jakarta None None None NA% None None None None None None None None Seoul Broadcasting System (Current Investment,... 0.450000 44259784.000000 324202.917800 62.690000 6204924122.000000 45451069.193650 210.000000 None None None 1226285.218000 3937308.585000 2.227000 0.949000 576812.777000 1047028.354000 470215.577000 505070.855000 6179.398000 702642.890000 224320.000000 NaN 1006762.890000 2711023.367000 1009918.730000 187256.689000 455949.732000 70451.896000 97855.130000 70451.896000 20551.670000 -89533.613000 -145940.433000 9897787962.000000 38205.461533 3860.000000 3717786.257000 372089.069000 1096619.608000

15708 rows × 91 columns

# Rename kolom 'exchange_ticker' jadi 'ticker'
df_findat_final = df_findat_final.rename(columns={'exchange_ticker': 'ticker'})

# Hapus 'IDX:' prefix dari kolom 'ticker'
df_findat_final['ticker'] = df_findat_final['ticker'].astype(str).str.replace('IDX:', '', regex=False)

# sort DF berdasarkan 'ticker' asc, lalu 'Year' desc
df_findat_final = df_findat_final.sort_values(by=['ticker', 'Year'], ascending=[True, False]).reset_index(drop=True)

# Display
df_findat_final.head()
variable ticker Year Entity Name IPO Date MM/dd/yyyy Primary Industry Entity ID Exchange 1st Level Primary Industry Country / Region Name Address 1 Web Address Business Description Company Type Investor Type Company Status Ownership Structure Activist Investor? Yes/No Institution Coverage Level Consolidation Status Has Current Financials? Yes/No Has Bank Regulatory Financials? Yes/No Has Third Party Financials? Yes/No Long Business Description Topic Tags Crunchbase Categories Year Established Date Established MM/dd/yyyy Year Incorporated Date Incorporated MM/dd/yyyy Month of Fiscal Year End Headcount As Of MM/dd/yyyy Industry Classification 2nd Level Primary Industry 3rd Level Primary Industry 4th Level Primary Industry Sector Industry Group Industry City Parent Company Name Parent Ticker Parent Exchange: Ticker Parent Percent Owned (%) Parent City Parent Industry Ultimate Parent Company Name Ultimate Parent Ticker Ultimate Parent Exchange: Ticker Ultimate Parent City Ultimate Parent Industry Ultimate Parent Country / Region All Investors Percent Owned - All Institutions (%) Shares Owned - All Institutions (actual) Market Value - All Institutions (Rp.B) Percent Owned - Insiders (%) Shares Owned - Insiders (actual) Market Value - Insiders (Rp.B) IPO Price (Rp.) Location Type Data Precision Data Year Total Liabilities (Rp.M) Total Assets (Rp.M) Current Ratio (x) Quick Ratio (x) Working Capital (Rp.M) Total Current Assets (Rp.M) Total Current Liabilities (Rp.M) Inventory (Rp.M) Prepaid Exp. (Rp.M) Long-term Debt (Rp.M) Short-term Borrowings (Rp.M) Current Portion of LT Debt & Leases (Rp.M) Total Debt (Rp.M) Total Equity (Rp.M) Net Property, Plant & Equipment (Rp.M) Cost Of Goods Sold (Rp.M) Total Revenue (Rp.M) Operating Income (Rp.M) EBITDA (Rp.M) EBIT (Rp.M) Net Income to Company (Rp.M) Cash from Ops. (Rp.M) Net Change in Cash (Rp.M) ECS Total Common Shares Outstanding (actual) Market Capitalization (Rp.B) Day Close Price (Rp.) Total Capital (Rp.M) Cash & Short-term Investments (Rp.M) Net Intangibles (Rp.M)
0 AADI 2024 PT Adaro Andalan Indonesia Tbk (IDX:AADI) 2024-12-03 Coal and Consumable Fuels 4422654 IDX Energy and Utilities Indonesia Cyber 2 Tower www.adaroindonesia.com PT Adaro Andalan Indonesia Tbk engages in the ... Public Company None Operating None No None None Yes No No None Freight Service; Marine Transportation; Mining... None 2004.000000 2004-12-01 00:00:00 NaN NaT None 2025-09-01 Coal and Consumable Fuels Oil, Gas and Coal Coal and Consumable Fuels None Energy Energy Oil, Gas and Consumable Fuels Jakarta None None None NA% None None None None None None None None PT Alamtri Resources Indonesia Tbk (Current In... 1.890000 147513763.000000 1143231.663250 5.870000 456756180.000000 3539860.395000 5550.000000 None None None 42324475.248000 96469808.484000 2.537000 2.292000 21593100.594000 35641809.096000 14048708.502000 1103501.802000 105989.232000 23185756.224000 NaN NaN 23924525.640000 54145333.236000 22391497.002000 61072270.545424 84304634.017260 18392374.252099 19927346.291702 18392374.252099 21026086.808611 18994042.847576 -16098252.230656 7786891760.000000 65993.907666 8475.000000 78069858.876000 25454061.012000 37588.830000
1 AADI 2023 PT Adaro Andalan Indonesia Tbk (IDX:AADI) 2024-12-03 Coal and Consumable Fuels 4422654 IDX Energy and Utilities Indonesia Cyber 2 Tower www.adaroindonesia.com PT Adaro Andalan Indonesia Tbk engages in the ... Public Company None Operating None No None None Yes No No None Freight Service; Marine Transportation; Mining... None 2004.000000 2004-12-01 00:00:00 NaN NaT None 2025-09-01 Coal and Consumable Fuels Oil, Gas and Coal Coal and Consumable Fuels None Energy Energy Oil, Gas and Consumable Fuels Jakarta None None None NA% None None None None None None None None PT Alamtri Resources Indonesia Tbk (Current In... 1.890000 147513763.000000 1143231.663250 5.870000 456756180.000000 3539860.395000 5550.000000 None None None 35027575.128000 108867787.224000 1.818000 1.699000 22687219.212000 50406307.896000 27719088.684000 1003297.260000 66758.034000 2978539.704000 NaN NaN 14784754.278000 73840212.096000 14962801.392000 63789705.022615 90132389.148923 21711813.969577 23121835.271885 21711813.969577 19592972.794962 9069085.144385 -12888255.983885 7008202560.000000 NaN NaN 88624966.374000 39810523.914000 35390.544000
2 AADI 2022 PT Adaro Andalan Indonesia Tbk (IDX:AADI) 2024-12-03 Coal and Consumable Fuels 4422654 IDX Energy and Utilities Indonesia Cyber 2 Tower www.adaroindonesia.com PT Adaro Andalan Indonesia Tbk engages in the ... Public Company None Operating None No None None Yes No No None Freight Service; Marine Transportation; Mining... None 2004.000000 2004-12-01 00:00:00 NaN NaT None 2025-09-01 Coal and Consumable Fuels Oil, Gas and Coal Coal and Consumable Fuels None Energy Energy Oil, Gas and Consumable Fuels Jakarta None None None NA% None None None None None None None None PT Alamtri Resources Indonesia Tbk (Current In... 1.890000 147513763.000000 1143231.663250 5.870000 456756180.000000 3539860.395000 5550.000000 None None None 53697263.230000 123705349.494000 2.043000 1.960000 34306837.619000 67190488.975000 32883651.356000 1864970.784000 10124.908000 16805809.909000 NaN NaN 18638045.561000 70008086.264000 13750882.913000 56091453.934708 114740624.204931 52675640.780862 57656683.481331 52675640.780862 34894343.232646 46564112.440085 27880000.623946 7008203000.000000 NaN NaN 88646131.825000 54122633.598000 32610.900000
3 AADI 2021 PT Adaro Andalan Indonesia Tbk (IDX:AADI) 2024-12-03 Coal and Consumable Fuels 4422654 IDX Energy and Utilities Indonesia Cyber 2 Tower www.adaroindonesia.com PT Adaro Andalan Indonesia Tbk engages in the ... Public Company None Operating None No None None Yes No No None Freight Service; Marine Transportation; Mining... None 2004.000000 2004-12-01 00:00:00 NaN NaT None 2025-09-01 Coal and Consumable Fuels Oil, Gas and Coal Coal and Consumable Fuels None Energy Energy Oil, Gas and Consumable Fuels Jakarta None None None NA% None None None None None None None None PT Alamtri Resources Indonesia Tbk (Current In... 1.890000 147513763.000000 1143231.663250 5.870000 456756180.000000 3539860.395000 5550.000000 None None None 36597123.720000 88200470.070000 2.217000 2.091000 18995952.510000 34608541.707000 15612589.197000 1033762.320000 95027.490000 16900161.822000 NaN NaN 18489656.871000 51603346.350000 15934956.066000 33903719.536034 55264915.482548 18421357.075862 23354567.767490 18421357.075862 11866461.387490 15245298.528812 5766604.814061 7008203000.000000 NaN NaN 70093003.221000 24558551.190000 6154.704000
4 AADI 2020 PT Adaro Andalan Indonesia Tbk (IDX:AADI) 2024-12-03 Coal and Consumable Fuels 4422654 IDX Energy and Utilities Indonesia Cyber 2 Tower www.adaroindonesia.com PT Adaro Andalan Indonesia Tbk engages in the ... Public Company None Operating None No None None Yes No No None Freight Service; Marine Transportation; Mining... None 2004.000000 2004-12-01 00:00:00 NaN NaT None 2025-09-01 Coal and Consumable Fuels Oil, Gas and Coal Coal and Consumable Fuels None Energy Energy Oil, Gas and Consumable Fuels Jakarta None None None NA% None None None None None None None None PT Alamtri Resources Indonesia Tbk (Current In... 1.890000 147513763.000000 1143231.663250 5.870000 456756180.000000 3539860.395000 5550.000000 None None None NaN NaN None None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

Ubah Nama Kolom

df_findat_final = df_findat_final.rename(columns={'Day Close Price (Rp.)': 'Year Close Stock Price (Rp.)'})
df_findat_final.head()
variable ticker Year Entity Name IPO Date MM/dd/yyyy Primary Industry Entity ID Exchange 1st Level Primary Industry Country / Region Name Address 1 Web Address Business Description Company Type Investor Type Company Status Ownership Structure Activist Investor? Yes/No Institution Coverage Level Consolidation Status Has Current Financials? Yes/No Has Bank Regulatory Financials? Yes/No Has Third Party Financials? Yes/No Long Business Description Topic Tags Crunchbase Categories Year Established Date Established MM/dd/yyyy Year Incorporated Date Incorporated MM/dd/yyyy Month of Fiscal Year End Headcount As Of MM/dd/yyyy Industry Classification 2nd Level Primary Industry 3rd Level Primary Industry 4th Level Primary Industry Sector Industry Group Industry City Parent Company Name Parent Ticker Parent Exchange: Ticker Parent Percent Owned (%) Parent City Parent Industry Ultimate Parent Company Name Ultimate Parent Ticker Ultimate Parent Exchange: Ticker Ultimate Parent City Ultimate Parent Industry Ultimate Parent Country / Region All Investors Percent Owned - All Institutions (%) Shares Owned - All Institutions (actual) Market Value - All Institutions (Rp.B) Percent Owned - Insiders (%) Shares Owned - Insiders (actual) Market Value - Insiders (Rp.B) IPO Price (Rp.) Location Type Data Precision Data Year Total Liabilities (Rp.M) Total Assets (Rp.M) Current Ratio (x) Quick Ratio (x) Working Capital (Rp.M) Total Current Assets (Rp.M) Total Current Liabilities (Rp.M) Inventory (Rp.M) Prepaid Exp. (Rp.M) Long-term Debt (Rp.M) Short-term Borrowings (Rp.M) Current Portion of LT Debt & Leases (Rp.M) Total Debt (Rp.M) Total Equity (Rp.M) Net Property, Plant & Equipment (Rp.M) Cost Of Goods Sold (Rp.M) Total Revenue (Rp.M) Operating Income (Rp.M) EBITDA (Rp.M) EBIT (Rp.M) Net Income to Company (Rp.M) Cash from Ops. (Rp.M) Net Change in Cash (Rp.M) ECS Total Common Shares Outstanding (actual) Market Capitalization (Rp.B) Year Close Stock Price (Rp.) Total Capital (Rp.M) Cash & Short-term Investments (Rp.M) Net Intangibles (Rp.M)
0 AADI 2024 PT Adaro Andalan Indonesia Tbk (IDX:AADI) 2024-12-03 Coal and Consumable Fuels 4422654 IDX Energy and Utilities Indonesia Cyber 2 Tower www.adaroindonesia.com PT Adaro Andalan Indonesia Tbk engages in the ... Public Company None Operating None No None None Yes No No None Freight Service; Marine Transportation; Mining... None 2004.000000 2004-12-01 00:00:00 NaN NaT None 2025-09-01 Coal and Consumable Fuels Oil, Gas and Coal Coal and Consumable Fuels None Energy Energy Oil, Gas and Consumable Fuels Jakarta None None None NA% None None None None None None None None PT Alamtri Resources Indonesia Tbk (Current In... 1.890000 147513763.000000 1143231.663250 5.870000 456756180.000000 3539860.395000 5550.000000 None None None 42324475.248000 96469808.484000 2.537000 2.292000 21593100.594000 35641809.096000 14048708.502000 1103501.802000 105989.232000 23185756.224000 NaN NaN 23924525.640000 54145333.236000 22391497.002000 61072270.545424 84304634.017260 18392374.252099 19927346.291702 18392374.252099 21026086.808611 18994042.847576 -16098252.230656 7786891760.000000 65993.907666 8475.000000 78069858.876000 25454061.012000 37588.830000
1 AADI 2023 PT Adaro Andalan Indonesia Tbk (IDX:AADI) 2024-12-03 Coal and Consumable Fuels 4422654 IDX Energy and Utilities Indonesia Cyber 2 Tower www.adaroindonesia.com PT Adaro Andalan Indonesia Tbk engages in the ... Public Company None Operating None No None None Yes No No None Freight Service; Marine Transportation; Mining... None 2004.000000 2004-12-01 00:00:00 NaN NaT None 2025-09-01 Coal and Consumable Fuels Oil, Gas and Coal Coal and Consumable Fuels None Energy Energy Oil, Gas and Consumable Fuels Jakarta None None None NA% None None None None None None None None PT Alamtri Resources Indonesia Tbk (Current In... 1.890000 147513763.000000 1143231.663250 5.870000 456756180.000000 3539860.395000 5550.000000 None None None 35027575.128000 108867787.224000 1.818000 1.699000 22687219.212000 50406307.896000 27719088.684000 1003297.260000 66758.034000 2978539.704000 NaN NaN 14784754.278000 73840212.096000 14962801.392000 63789705.022615 90132389.148923 21711813.969577 23121835.271885 21711813.969577 19592972.794962 9069085.144385 -12888255.983885 7008202560.000000 NaN NaN 88624966.374000 39810523.914000 35390.544000
2 AADI 2022 PT Adaro Andalan Indonesia Tbk (IDX:AADI) 2024-12-03 Coal and Consumable Fuels 4422654 IDX Energy and Utilities Indonesia Cyber 2 Tower www.adaroindonesia.com PT Adaro Andalan Indonesia Tbk engages in the ... Public Company None Operating None No None None Yes No No None Freight Service; Marine Transportation; Mining... None 2004.000000 2004-12-01 00:00:00 NaN NaT None 2025-09-01 Coal and Consumable Fuels Oil, Gas and Coal Coal and Consumable Fuels None Energy Energy Oil, Gas and Consumable Fuels Jakarta None None None NA% None None None None None None None None PT Alamtri Resources Indonesia Tbk (Current In... 1.890000 147513763.000000 1143231.663250 5.870000 456756180.000000 3539860.395000 5550.000000 None None None 53697263.230000 123705349.494000 2.043000 1.960000 34306837.619000 67190488.975000 32883651.356000 1864970.784000 10124.908000 16805809.909000 NaN NaN 18638045.561000 70008086.264000 13750882.913000 56091453.934708 114740624.204931 52675640.780862 57656683.481331 52675640.780862 34894343.232646 46564112.440085 27880000.623946 7008203000.000000 NaN NaN 88646131.825000 54122633.598000 32610.900000
3 AADI 2021 PT Adaro Andalan Indonesia Tbk (IDX:AADI) 2024-12-03 Coal and Consumable Fuels 4422654 IDX Energy and Utilities Indonesia Cyber 2 Tower www.adaroindonesia.com PT Adaro Andalan Indonesia Tbk engages in the ... Public Company None Operating None No None None Yes No No None Freight Service; Marine Transportation; Mining... None 2004.000000 2004-12-01 00:00:00 NaN NaT None 2025-09-01 Coal and Consumable Fuels Oil, Gas and Coal Coal and Consumable Fuels None Energy Energy Oil, Gas and Consumable Fuels Jakarta None None None NA% None None None None None None None None PT Alamtri Resources Indonesia Tbk (Current In... 1.890000 147513763.000000 1143231.663250 5.870000 456756180.000000 3539860.395000 5550.000000 None None None 36597123.720000 88200470.070000 2.217000 2.091000 18995952.510000 34608541.707000 15612589.197000 1033762.320000 95027.490000 16900161.822000 NaN NaN 18489656.871000 51603346.350000 15934956.066000 33903719.536034 55264915.482548 18421357.075862 23354567.767490 18421357.075862 11866461.387490 15245298.528812 5766604.814061 7008203000.000000 NaN NaN 70093003.221000 24558551.190000 6154.704000
4 AADI 2020 PT Adaro Andalan Indonesia Tbk (IDX:AADI) 2024-12-03 Coal and Consumable Fuels 4422654 IDX Energy and Utilities Indonesia Cyber 2 Tower www.adaroindonesia.com PT Adaro Andalan Indonesia Tbk engages in the ... Public Company None Operating None No None None Yes No No None Freight Service; Marine Transportation; Mining... None 2004.000000 2004-12-01 00:00:00 NaN NaT None 2025-09-01 Coal and Consumable Fuels Oil, Gas and Coal Coal and Consumable Fuels None Energy Energy Oil, Gas and Consumable Fuels Jakarta None None None NA% None None None None None None None None PT Alamtri Resources Indonesia Tbk (Current In... 1.890000 147513763.000000 1143231.663250 5.870000 456756180.000000 3539860.395000 5550.000000 None None None NaN NaN None None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

Cek Saham Tersedia di Financial Data

list_symbol_findat = df_findat_final['ticker'].unique().tolist()
print(list_symbol_findat)
print(len(list_symbol_findat))
['AADI', 'AALI', 'ABBA', 'ABDA', 'ABMM', 'ACES', 'ACRO', 'ACST', 'ADCP', 'ADES', 'ADHI', 'ADMF', 'ADMG', 'ADMR', 'ADRO', 'AEGS', 'AGAR', 'AGII', 'AGRO', 'AGRS', 'AHAP', 'AIMS', 'AISA', 'AKKU', 'AKPI', 'AKRA', 'AKSI', 'ALDO', 'ALII', 'ALKA', 'ALMI', 'ALTO', 'AMAG', 'AMAN', 'AMAR', 'AMFG', 'AMIN', 'AMMN', 'AMMS', 'AMOR', 'AMRT', 'ANDI', 'ANJT', 'ANTM', 'APEX', 'APIC', 'APII', 'APLI', 'APLN', 'ARCI', 'AREA', 'ARGO', 'ARII', 'ARKA', 'ARKO', 'ARNA', 'ARTA', 'ARTI', 'ARTO', 'ASBI', 'ASDM', 'ASGR', 'ASHA', 'ASII', 'ASJT', 'ASLC', 'ASLI', 'ASMI', 'ASPI', 'ASPR', 'ASRI', 'ASRM', 'ASSA', 'ATAP', 'ATIC', 'ATLA', 'AUTO', 'AVIA', 'AWAN', 'AXIO', 'AYAM', 'AYLS', 'BABP', 'BABY', 'BACA', 'BAIK', 'BAJA', 'BALI', 'BANK', 'BAPA', 'BAPI', 'BATA', 'BATR', 'BAUT', 'BAYU', 'BBCA', 'BBHI', 'BBKP', 'BBLD', 'BBMD', 'BBNI', 'BBRI', 'BBRM', 'BBSI', 'BBSS', 'BBTN', 'BBYB', 'BCAP', 'BCIC', 'BCIP', 'BDKR', 'BDMN', 'BEBS', 'BEEF', 'BEER', 'BEKS', 'BELI', 'BELL', 'BESS', 'BEST', 'BFIN', 'BGTG', 'BHAT', 'BHIT', 'BIKA', 'BIKE', 'BIMA', 'BINA', 'BINO', 'BIPI', 'BIPP', 'BIRD', 'BISI', 'BJBR', 'BJTM', 'BKDP', 'BKSL', 'BKSW', 'BLES', 'BLOG', 'BLTA', 'BLTZ', 'BLUE', 'BMAS', 'BMBL', 'BMHS', 'BMRI', 'BMSR', 'BMTR', 'BNBA', 'BNBR', 'BNGA', 'BNII', 'BNLI', 'BOAT', 'BOBA', 'BOGA', 'BOLA', 'BOLT', 'BOSS', 'BPFI', 'BPII', 'BPTR', 'BRAM', 'BREN', 'BRIS', 'BRMS', 'BRNA', 'BRPT', 'BRRC', 'BSBK', 'BSDE', 'BSIM', 'BSML', 'BSSR', 'BSWD', 'BTEK', 'BTON', 'BTPN', 'BTPS', 'BUAH', 'BUDI', 'BUKA', 'BUKK', 'BULL', 'BUMI', 'BUVA', 'BVIC', 'BWPT', 'BYAN', 'CAKK', 'CAMP', 'CANI', 'CARE', 'CARS', 'CASA', 'CASH', 'CASS', 'CBDK', 'CBPE', 'CBRE', 'CBUT', 'CCSI', 'CDIA', 'CEKA', 'CENT', 'CFIN', 'CGAS', 'CHEK', 'CHEM', 'CHIP', 'CINT', 'CITA', 'CITY', 'CLAY', 'CLEO', 'CLPI', 'CMNP', 'CMNT', 'CMPP', 'CMRY', 'CNKO', 'CNMA', 'COAL', 'COCO', 'COIN', 'CPIN', 'CPRI', 'CPRO', 'CRAB', 'CRSN', 'CSAP', 'CSIS', 'CSMI', 'CSRA', 'CTBN', 'CTRA', 'CTTH', 'CUAN', 'CYBR', 'DAAZ', 'DADA', 'DART', 'DATA', 'DAYA', 'DCII', 'DEAL', 'DEFI', 'DEPO', 'DEWA', 'DEWI', 'DFAM', 'DGIK', 'DGNS', 'DGWG', 'DIGI', 'DILD', 'DIVA', 'DKFT', 'DKHH', 'DLTA', 'DMAS', 'DMMX', 'DMND', 'DNAR', 'DNET', 'DOID', 'DOOH', 'DOSS', 'DPNS', 'DPUM', 'DRMA', 'DSFI', 'DSNG', 'DSSA', 'DUTI', 'DVLA', 'DWGL', 'DYAN', 'EAST', 'ECII', 'EDGE', 'EKAD', 'ELIT', 'ELPI', 'ELSA', 'ELTY', 'EMAS', 'EMDE', 'EMTK', 'ENAK', 'ENRG', 'ENZO', 'EPAC', 'EPMT', 'ERAA', 'ERAL', 'ERTX', 'ESIP', 'ESSA', 'ESTA', 'ESTI', 'ETWA', 'EURO', 'EXCL', 'FAPA', 'FAST', 'FASW', 'FILM', 'FIMP', 'FIRE', 'FISH', 'FITT', 'FLMC', 'FMII', 'FOLK', 'FOOD', 'FORE', 'FORU', 'FPNI', 'FUJI', 'FUTR', 'FWCT', 'GAMA', 'GDST', 'GDYR', 'GEMA', 'GEMS', 'GGRM', 'GGRP', 'GHON', 'GIAA', 'GJTL', 'GLOB', 'GLVA', 'GMFI', 'GMTD', 'GOLD', 'GOLF', 'GOOD', 'GOTO', 'GPRA', 'GPSO', 'GRIA', 'GRPH', 'GRPM', 'GSMF', 'GTBO', 'GTRA', 'GTSI', 'GULA', 'GUNA', 'GWSA', 'GZCO', 'HADE', 'HAIS', 'HAJJ', 'HALO', 'HATM', 'HBAT', 'HDFA', 'HDIT', 'HEAL', 'HELI', 'HERO', 'HEXA', 'HGII', 'HILL', 'HITS', 'HKMU', 'HMSP', 'HOKI', 'HOMI', 'HOPE', 'HRME', 'HRTA', 'HRUM', 'HUMI', 'HYGN', 'IATA', 'IBFN', 'IBOS', 'IBST', 'ICBP', 'ICON', 'IDEA', 'IDPR', 'IFII', 'IFSH', 'IGAR', 'IKAI', 'IKAN', 'IKBI', 'IKPM', 'IMAS', 'IMJS', 'IMPC', 'INAF', 'INAI', 'INCF', 'INCI', 'INCO', 'INDF', 'INDO', 'INDR', 'INDS', 'INDX', 'INDY', 'INET', 'INKP', 'INOV', 'INPC', 'INPP', 'INPS', 'INRU', 'INTA', 'INTD', 'INTP', 'IOTF', 'IPAC', 'IPCC', 'IPCM', 'IPOL', 'IPPE', 'IPTV', 'IRRA', 'IRSX', 'ISAP', 'ISAT', 'ISEA', 'ISSP', 'ITIC', 'ITMA', 'ITMG', 'JARR', 'JAST', 'JATI', 'JAWA', 'JAYA', 'JECC', 'JGLE', 'JIHD', 'JKON', 'JMAS', 'JPFA', 'JRPT', 'JSMR', 'JSPT', 'JTPE', 'KAEF', 'KAQI', 'KARW', 'KAYU', 'KBAG', 'KBLI', 'KBLM', 'KBLV', 'KDSI', 'KDTN', 'KEEN', 'KEJU', 'KETR', 'KIAS', 'KICI', 'KIJA', 'KING', 'KINO', 'KIOS', 'KJEN', 'KKES', 'KKGI', 'KLAS', 'KLBF', 'KLIN', 'KMDS', 'KMTR', 'KOBX', 'KOCI', 'KOIN', 'KOKA', 'KONI', 'KOPI', 'KOTA', 'KPIG', 'KRAS', 'KREN', 'KRYA', 'KSIX', 'KUAS', 'LABA', 'LABS', 'LAJU', 'LAND', 'LAPD', 'LCKM', 'LEAD', 'LFLO', 'LIFE', 'LINK', 'LION', 'LIVE', 'LMAX', 'LMPI', 'LMSH', 'LOPI', 'LPCK', 'LPGI', 'LPIN', 'LPKR', 'LPLI', 'LPPF', 'LPPS', 'LRNA', 'LSIP', 'LTLS', 'LUCK', 'LUCY', 'MAHA', 'MAIN', 'MANG', 'MAPA', 'MAPB', 'MAPI', 'MARI', 'MARK', 'MASB', 'MAXI', 'MAYA', 'MBAP', 'MBMA', 'MBSS', 'MBTO', 'MCAS', 'MCOL', 'MCOR', 'MDIA', 'MDIY', 'MDKA', 'MDKI', 'MDLA', 'MDLN', 'MDRN', 'MEDC', 'MEDS', 'MEGA', 'MEJA', 'MENN', 'MERI', 'MERK', 'META', 'MFMI', 'MGLV', 'MGNA', 'MGRO', 'MHKI', 'MICE', 'MIDI', 'MIKA', 'MINA', 'MINE', 'MIRA', 'MITI', 'MKAP', 'MKNT', 'MKPI', 'MKTR', 'MLBI', 'MLIA', 'MLPL', 'MLPT', 'MMIX', 'MMLP', 'MNCN', 'MOLI', 'MORA', 'MPIX', 'MPMX', 'MPOW', 'MPPA', 'MPRO', 'MPXL', 'MRAT', 'MREI', 'MSIE', 'MSIN', 'MSJA', 'MSKY', 'MSTI', 'MTDL', 'MTEL', 'MTFN', 'MTLA', 'MTMH', 'MTPS', 'MTSM', 'MTWI', 'MUTU', 'MYOH', 'MYOR', 'MYTX', 'NAIK', 'NANO', 'NASA', 'NASI', 'NATO', 'NAYZ', 'NCKL', 'NELY', 'NEST', 'NETV', 'NFCX', 'NICE', 'NICK', 'NICL', 'NIKL', 'NINE', 'NIRO', 'NISP', 'NOBU', 'NPGF', 'NRCA', 'NSSS', 'NTBK', 'NZIA', 'OASA', 'OBAT', 'OBMD', 'OILS', 'OKAS', 'OLIV', 'OMED', 'OMRE', 'OPMS', 'PACK', 'PADA', 'PADI', 'PALM', 'PAMG', 'PANI', 'PANR', 'PANS', 'PART', 'PBID', 'PBRX', 'PBSA', 'PCAR', 'PDES', 'PDPP', 'PEGE', 'PEHA', 'PEVE', 'PGAS', 'PGEO', 'PGJO', 'PGLI', 'PGUN', 'PICO', 'PIPA', 'PJAA', 'PJHB', 'PKPK', 'PLAN', 'PLIN', 'PMJS', 'PMMP', 'PMUI', 'PNBN', 'PNBS', 'PNGO', 'PNIN', 'PNLF', 'PNSE', 'POLA', 'POLI', 'POLL', 'POLU', 'POLY', 'PORT', 'POWR', 'PPGL', 'PPRE', 'PPRI', 'PPRO', 'PRAY', 'PRDA', 'PRIM', 'PSAB', 'PSAT', 'PSDN', 'PSGO', 'PSKT', 'PSSI', 'PTBA', 'PTDU', 'PTIS', 'PTMP', 'PTMR', 'PTPP', 'PTPS', 'PTPW', 'PTRO', 'PTSN', 'PTSP', 'PUDP', 'PURA', 'PURI', 'PWON', 'PYFA', 'PZZA', 'RAAM', 'RAFI', 'RAJA', 'RALS', 'RANC', 'RATU', 'RBMS', 'RCCC', 'RDTX', 'REAL', 'RELF', 'RELI', 'RGAS', 'RICY', 'RIGS', 'RISE', 'RMKE', 'RMKO', 'ROCK', 'RODA', 'RONY', 'ROTI', 'RSCH', 'RSGK', 'RUIS', 'RUNS', 'SAFE', 'SAGE', 'SAME', 'SAMF', 'SAPX', 'SATU', 'SBAT', 'SBMA', 'SCCO', 'SCMA', 'SCNP', 'SDMU', 'SDPC', 'SDRA', 'SEMA', 'SFAN', 'SGER', 'SGRO', 'SHID', 'SHIP', 'SICO', 'SIDO', 'SILO', 'SIMP', 'SINI', 'SIPD', 'SKBM', 'SKLT', 'SKRN', 'SLIS', 'SMAR', 'SMBR', 'SMCB', 'SMDM', 'SMDR', 'SMGA', 'SMGR', 'SMIL', 'SMKL', 'SMKM', 'SMLE', 'SMMA', 'SMMT', 'SMRA', 'SMSM', 'SNLK', 'SOCI', 'SOFA', 'SOHO', 'SOLA', 'SONA', 'SOSS', 'SOTS', 'SOUL', 'SPMA', 'SPRE', 'SPTO', 'SQMI', 'SRAJ', 'SRSN', 'SRTG', 'SSIA', 'SSMS', 'SSTM', 'STAA', 'STAR', 'STRK', 'STTP', 'SULI', 'SUNI', 'SUPR', 'SURE', 'SURI', 'SWAT', 'SWID', 'TALF', 'TAMA', 'TAMU', 'TAPG', 'TARA', 'TAXI', 'TAYS', 'TBIG', 'TBLA', 'TBMS', 'TCID', 'TCPI', 'TEBE', 'TECH', 'TELE', 'TFAS', 'TFCO', 'TGKA', 'TGRA', 'TGUK', 'TIFA', 'TINS', 'TIRA', 'TIRT', 'TKIM', 'TLDN', 'TLKM', 'TMAS', 'TMPO', 'TNCA', 'TOBA', 'TOOL', 'TOPS', 'TOSK', 'TOTL', 'TOTO', 'TOWR', 'TOYS', 'TPIA', 'TPMA', 'TRGU', 'TRIM', 'TRIN', 'TRIS', 'TRJA', 'TRON', 'TRST', 'TRUE', 'TRUK', 'TRUS', 'TSPC', 'TUGU', 'TYRE', 'UANG', 'UCID', 'UDNG', 'UFOE', 'ULTJ', 'UNIC', 'UNIQ', 'UNSP', 'UNTD', 'UNTR', 'UNVR', 'URBN', 'UVCR', 'VAST', 'VERN', 'VICI', 'VICO', 'VINS', 'VISI', 'VIVA', 'VKTR', 'VOKS', 'VRNA', 'VTNY', 'WAPO', 'WEGE', 'WEHA', 'WGSH', 'WICO', 'WIDI', 'WIFI', 'WIIM', 'WIKA', 'WINE', 'WINR', 'WINS', 'WIRG', 'WMPP', 'WMUU', 'WOMF', 'WOOD', 'WOWS', 'WSBP', 'WSKT', 'WTON', 'XDIF', 'XIFE', 'XISB', 'XMGB', 'XMLF', 'XPSG', 'YELO', 'YOII', 'YPAS', 'YULE', 'YUPI', 'ZATA', 'ZBRA', 'ZINC', 'ZONE', 'ZYRX']
924

Hapus Efek Fund/Nonperusahaan (Ticker diawali X)

df_findat_final[df_findat_final['ticker'].str.startswith('X')]['Entity Name'].unique().tolist()
['PT Danareksa Investment Management - Danareksa ETF Indonesia Top 40 Fund (IDX:XDIF)',
 'Pt Indo Premier Investment Management - FTSE Indonesia ESG ETF (IDX:XIFE)',
 'Pt Indo Premier Investment Management - Reksa Dana Premier Etf Indonesia Sovereign Bonds (IDX:XISB)',
 'PT Majoris Asset Management - Government Bonds ETF (IDX:XMGB)',
 'Pt Mandiri Manajemen Investasi - Reksa Dana Indeks Mandiri Indeks LQ45 (IDX:XMLF)',
 'Pt Pinnacle Persada Investama - Pinnacle Indonesia Esg Etf (IDX:XPSG)']
df_findat_final = df_findat_final[~df_findat_final['ticker'].str.startswith('X')]
print("Cek Apakah Emiten Fund (Bukan Perusahaan) Masih Ada :")
df_findat_final[df_findat_final['ticker'].str.startswith('X')]['Entity Name'].unique().tolist()
Cek Apakah Emiten Fund (Bukan Perusahaan) Masih Ada :
[]

Hapus Perusahaan Baru IPO (2024-2025)

df_emiten_idx[df_emiten_idx['Tanggal Pencatatan'].dt.year.isin([2024, 2025])]
Kode Nama Perusahaan Tanggal Pencatatan Saham Papan Pencatatan Tanggal Masuk Tanggal Keluar Kriteria PPK Distress_PPK
884 ASLI Asri Karya Lestari Tbk. 2024-01-05 6.250.000.000 Pengembangan NaT NaT <NA> No No
885 CGAS Citra Nusantara Gemilang Tbk. 2024-01-08 1.771.499.039 Pengembangan 2024-03-04 2024-04-04 [10] Yes Yes
886 NICE Adhi Kartiko Pratama Tbk. 2024-01-09 6.082.020.000 Pengembangan NaT NaT <NA> No No
887 MSJA Multi Spunindo Jaya Tbk. 2024-01-10 5.882.352.900 Pengembangan NaT NaT <NA> No No
888 SMLE Sinergi Multi Lestarindo Tbk. 2024-01-10 2.328.153.048 Pengembangan NaT NaT <NA> No No
... ... ... ... ... ... ... ... ... ... ...
943 KAQI Jantra Grupo Indonesia Tbk. 2025-03-10 2.075.800.000 Pengembangan NaT NaT <NA> No No
944 YUPI Yupi Indo Jelly Gum Tbk. 2025-03-25 8.544.488.700 Utama NaT NaT <NA> No No
945 FORE Fore Kopi Indonesia Tbk. 2025-04-14 8.918.359.270 Pengembangan NaT NaT <NA> No No
946 MDLA Medela Potentia Tbk. 2025-04-15 14.012.825.000 Utama NaT NaT <NA> No No
947 DKHH Cipta Sarana Medika Tbk. 2025-05-08 2.550.000.000 Pengembangan 2025-07-22 2025-07-31 [10] Yes Yes

64 rows × 10 columns

list_perusahaan_baru_ipo = df_emiten_idx[df_emiten_idx['Tanggal Pencatatan'].dt.year.isin([2024, 2025])]['Kode'].tolist()
list_perusahaan_baru_ipo.append('PJHB')
list_perusahaan_baru_ipo
['ASLI',
 'CGAS',
 'NICE',
 'MSJA',
 'SMLE',
 'ACRO',
 'MANG',
 'GRPH',
 'SMGA',
 'UNTD',
 'TOSK',
 'MPIX',
 'ALII',
 'MKAP',
 'MEJA',
 'LIVE',
 'HYGN',
 'BAIK',
 'VISI',
 'AREA',
 'MHKI',
 'ATLA',
 'DATA',
 'SOLA',
 'BATR',
 'SPRE',
 'PART',
 'GOLF',
 'ISEA',
 'BLES',
 'GUNA',
 'LABS',
 'DOSS',
 'NEST',
 'PTMR',
 'VERN',
 'DAAZ',
 'BOAT',
 'NAIK',
 'AADI',
 'MDIY',
 'KSIX',
 'RATU',
 'YOII',
 'HGII',
 'BRRC',
 'DGWG',
 'CBDK',
 'OBAT',
 'MINE',
 'ASPR',
 'PSAT',
 'COIN',
 'CDIA',
 'BLOG',
 'MERI',
 'CHEK',
 'PMUI',
 'EMAS',
 'KAQI',
 'YUPI',
 'FORE',
 'MDLA',
 'DKHH',
 'PJHB']
print('Baris data finansial yang akan dihapus :')
df_findat_final[df_findat_final['ticker'].isin(list_perusahaan_baru_ipo)]
Baris data finansial yang akan dihapus :
variable ticker Year Entity Name IPO Date MM/dd/yyyy Primary Industry Entity ID Exchange 1st Level Primary Industry Country / Region Name Address 1 Web Address Business Description Company Type Investor Type Company Status Ownership Structure Activist Investor? Yes/No Institution Coverage Level Consolidation Status Has Current Financials? Yes/No Has Bank Regulatory Financials? Yes/No Has Third Party Financials? Yes/No Long Business Description Topic Tags Crunchbase Categories Year Established Date Established MM/dd/yyyy Year Incorporated Date Incorporated MM/dd/yyyy Month of Fiscal Year End Headcount As Of MM/dd/yyyy Industry Classification 2nd Level Primary Industry 3rd Level Primary Industry 4th Level Primary Industry Sector Industry Group Industry City Parent Company Name Parent Ticker Parent Exchange: Ticker Parent Percent Owned (%) Parent City Parent Industry Ultimate Parent Company Name Ultimate Parent Ticker Ultimate Parent Exchange: Ticker Ultimate Parent City Ultimate Parent Industry Ultimate Parent Country / Region All Investors Percent Owned - All Institutions (%) Shares Owned - All Institutions (actual) Market Value - All Institutions (Rp.B) Percent Owned - Insiders (%) Shares Owned - Insiders (actual) Market Value - Insiders (Rp.B) IPO Price (Rp.) Location Type Data Precision Data Year Total Liabilities (Rp.M) Total Assets (Rp.M) Current Ratio (x) Quick Ratio (x) Working Capital (Rp.M) Total Current Assets (Rp.M) Total Current Liabilities (Rp.M) Inventory (Rp.M) Prepaid Exp. (Rp.M) Long-term Debt (Rp.M) Short-term Borrowings (Rp.M) Current Portion of LT Debt & Leases (Rp.M) Total Debt (Rp.M) Total Equity (Rp.M) Net Property, Plant & Equipment (Rp.M) Cost Of Goods Sold (Rp.M) Total Revenue (Rp.M) Operating Income (Rp.M) EBITDA (Rp.M) EBIT (Rp.M) Net Income to Company (Rp.M) Cash from Ops. (Rp.M) Net Change in Cash (Rp.M) ECS Total Common Shares Outstanding (actual) Market Capitalization (Rp.B) Year Close Stock Price (Rp.) Total Capital (Rp.M) Cash & Short-term Investments (Rp.M) Net Intangibles (Rp.M)
0 AADI 2024 PT Adaro Andalan Indonesia Tbk (IDX:AADI) 2024-12-03 Coal and Consumable Fuels 4422654 IDX Energy and Utilities Indonesia Cyber 2 Tower www.adaroindonesia.com PT Adaro Andalan Indonesia Tbk engages in the ... Public Company None Operating None No None None Yes No No None Freight Service; Marine Transportation; Mining... None 2004.000000 2004-12-01 00:00:00 NaN NaT None 2025-09-01 Coal and Consumable Fuels Oil, Gas and Coal Coal and Consumable Fuels None Energy Energy Oil, Gas and Consumable Fuels Jakarta None None None NA% None None None None None None None None PT Alamtri Resources Indonesia Tbk (Current In... 1.890000 147513763.000000 1143231.663250 5.870000 456756180.000000 3539860.395000 5550.000000 None None None 42324475.248000 96469808.484000 2.537000 2.292000 21593100.594000 35641809.096000 14048708.502000 1103501.802000 105989.232000 23185756.224000 NaN NaN 23924525.640000 54145333.236000 22391497.002000 61072270.545424 84304634.017260 18392374.252099 19927346.291702 18392374.252099 21026086.808611 18994042.847576 -16098252.230656 7786891760.000000 65993.907666 8475.000000 78069858.876000 25454061.012000 37588.830000
1 AADI 2023 PT Adaro Andalan Indonesia Tbk (IDX:AADI) 2024-12-03 Coal and Consumable Fuels 4422654 IDX Energy and Utilities Indonesia Cyber 2 Tower www.adaroindonesia.com PT Adaro Andalan Indonesia Tbk engages in the ... Public Company None Operating None No None None Yes No No None Freight Service; Marine Transportation; Mining... None 2004.000000 2004-12-01 00:00:00 NaN NaT None 2025-09-01 Coal and Consumable Fuels Oil, Gas and Coal Coal and Consumable Fuels None Energy Energy Oil, Gas and Consumable Fuels Jakarta None None None NA% None None None None None None None None PT Alamtri Resources Indonesia Tbk (Current In... 1.890000 147513763.000000 1143231.663250 5.870000 456756180.000000 3539860.395000 5550.000000 None None None 35027575.128000 108867787.224000 1.818000 1.699000 22687219.212000 50406307.896000 27719088.684000 1003297.260000 66758.034000 2978539.704000 NaN NaN 14784754.278000 73840212.096000 14962801.392000 63789705.022615 90132389.148923 21711813.969577 23121835.271885 21711813.969577 19592972.794962 9069085.144385 -12888255.983885 7008202560.000000 NaN NaN 88624966.374000 39810523.914000 35390.544000
2 AADI 2022 PT Adaro Andalan Indonesia Tbk (IDX:AADI) 2024-12-03 Coal and Consumable Fuels 4422654 IDX Energy and Utilities Indonesia Cyber 2 Tower www.adaroindonesia.com PT Adaro Andalan Indonesia Tbk engages in the ... Public Company None Operating None No None None Yes No No None Freight Service; Marine Transportation; Mining... None 2004.000000 2004-12-01 00:00:00 NaN NaT None 2025-09-01 Coal and Consumable Fuels Oil, Gas and Coal Coal and Consumable Fuels None Energy Energy Oil, Gas and Consumable Fuels Jakarta None None None NA% None None None None None None None None PT Alamtri Resources Indonesia Tbk (Current In... 1.890000 147513763.000000 1143231.663250 5.870000 456756180.000000 3539860.395000 5550.000000 None None None 53697263.230000 123705349.494000 2.043000 1.960000 34306837.619000 67190488.975000 32883651.356000 1864970.784000 10124.908000 16805809.909000 NaN NaN 18638045.561000 70008086.264000 13750882.913000 56091453.934708 114740624.204931 52675640.780862 57656683.481331 52675640.780862 34894343.232646 46564112.440085 27880000.623946 7008203000.000000 NaN NaN 88646131.825000 54122633.598000 32610.900000
3 AADI 2021 PT Adaro Andalan Indonesia Tbk (IDX:AADI) 2024-12-03 Coal and Consumable Fuels 4422654 IDX Energy and Utilities Indonesia Cyber 2 Tower www.adaroindonesia.com PT Adaro Andalan Indonesia Tbk engages in the ... Public Company None Operating None No None None Yes No No None Freight Service; Marine Transportation; Mining... None 2004.000000 2004-12-01 00:00:00 NaN NaT None 2025-09-01 Coal and Consumable Fuels Oil, Gas and Coal Coal and Consumable Fuels None Energy Energy Oil, Gas and Consumable Fuels Jakarta None None None NA% None None None None None None None None PT Alamtri Resources Indonesia Tbk (Current In... 1.890000 147513763.000000 1143231.663250 5.870000 456756180.000000 3539860.395000 5550.000000 None None None 36597123.720000 88200470.070000 2.217000 2.091000 18995952.510000 34608541.707000 15612589.197000 1033762.320000 95027.490000 16900161.822000 NaN NaN 18489656.871000 51603346.350000 15934956.066000 33903719.536034 55264915.482548 18421357.075862 23354567.767490 18421357.075862 11866461.387490 15245298.528812 5766604.814061 7008203000.000000 NaN NaN 70093003.221000 24558551.190000 6154.704000
4 AADI 2020 PT Adaro Andalan Indonesia Tbk (IDX:AADI) 2024-12-03 Coal and Consumable Fuels 4422654 IDX Energy and Utilities Indonesia Cyber 2 Tower www.adaroindonesia.com PT Adaro Andalan Indonesia Tbk engages in the ... Public Company None Operating None No None None Yes No No None Freight Service; Marine Transportation; Mining... None 2004.000000 2004-12-01 00:00:00 NaN NaT None 2025-09-01 Coal and Consumable Fuels Oil, Gas and Coal Coal and Consumable Fuels None Energy Energy Oil, Gas and Consumable Fuels Jakarta None None None NA% None None None None None None None None PT Alamtri Resources Indonesia Tbk (Current In... 1.890000 147513763.000000 1143231.663250 5.870000 456756180.000000 3539860.395000 5550.000000 None None None NaN NaN None None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
15618 YUPI 2012 PT Yupi Indo Jelly Gum Tbk (IDX:YUPI) 2025-03-21 Packaged Foods and Meats Producers 8608372 IDX Consumer Indonesia Gedung Mugi Griya Lt 4 www.yupindo.com PT Yupi Indo Jelly Gum Tbk manufactures and se... Public Company None Operating Subsidiary None No None None Yes No No None Fast-Moving Consumer Goods; Packaging Services... None 1995.000000 1995-07-06 00:00:00 NaN NaT None 2025-09-01 Packaged Foods and Meats Producers; Confection... Producers Packaged Foods and Meats Producers None Consumer Staples Food, Beverage and Tobacco Food Products Jakarta Selatan PT Sweets Indonesia None None 0.899000 Jakarta Selatan Holding Companies, Patent Owners, and Trusts o... PT Sweets Indonesia None None Jakarta Selatan Holding Companies, Patent Owners, and Trusts o... Indonesia Affinity Equity Partners Limited (Current Inve... NaN NaN NaN NaN NaN NaN 2390.000000 None None None NaN NaN None None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
15619 YUPI 2011 PT Yupi Indo Jelly Gum Tbk (IDX:YUPI) 2025-03-21 Packaged Foods and Meats Producers 8608372 IDX Consumer Indonesia Gedung Mugi Griya Lt 4 www.yupindo.com PT Yupi Indo Jelly Gum Tbk manufactures and se... Public Company None Operating Subsidiary None No None None Yes No No None Fast-Moving Consumer Goods; Packaging Services... None 1995.000000 1995-07-06 00:00:00 NaN NaT None 2025-09-01 Packaged Foods and Meats Producers; Confection... Producers Packaged Foods and Meats Producers None Consumer Staples Food, Beverage and Tobacco Food Products Jakarta Selatan PT Sweets Indonesia None None 0.899000 Jakarta Selatan Holding Companies, Patent Owners, and Trusts o... PT Sweets Indonesia None None Jakarta Selatan Holding Companies, Patent Owners, and Trusts o... Indonesia Affinity Equity Partners Limited (Current Inve... NaN NaN NaN NaN NaN NaN 2390.000000 None None None NaN NaN None None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
15620 YUPI 2010 PT Yupi Indo Jelly Gum Tbk (IDX:YUPI) 2025-03-21 Packaged Foods and Meats Producers 8608372 IDX Consumer Indonesia Gedung Mugi Griya Lt 4 www.yupindo.com PT Yupi Indo Jelly Gum Tbk manufactures and se... Public Company None Operating Subsidiary None No None None Yes No No None Fast-Moving Consumer Goods; Packaging Services... None 1995.000000 1995-07-06 00:00:00 NaN NaT None 2025-09-01 Packaged Foods and Meats Producers; Confection... Producers Packaged Foods and Meats Producers None Consumer Staples Food, Beverage and Tobacco Food Products Jakarta Selatan PT Sweets Indonesia None None 0.899000 Jakarta Selatan Holding Companies, Patent Owners, and Trusts o... PT Sweets Indonesia None None Jakarta Selatan Holding Companies, Patent Owners, and Trusts o... Indonesia Affinity Equity Partners Limited (Current Inve... NaN NaN NaN NaN NaN NaN 2390.000000 None None None NaN NaN None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
15621 YUPI 2009 PT Yupi Indo Jelly Gum Tbk (IDX:YUPI) 2025-03-21 Packaged Foods and Meats Producers 8608372 IDX Consumer Indonesia Gedung Mugi Griya Lt 4 www.yupindo.com PT Yupi Indo Jelly Gum Tbk manufactures and se... Public Company None Operating Subsidiary None No None None Yes No No None Fast-Moving Consumer Goods; Packaging Services... None 1995.000000 1995-07-06 00:00:00 NaN NaT None 2025-09-01 Packaged Foods and Meats Producers; Confection... Producers Packaged Foods and Meats Producers None Consumer Staples Food, Beverage and Tobacco Food Products Jakarta Selatan PT Sweets Indonesia None None 0.899000 Jakarta Selatan Holding Companies, Patent Owners, and Trusts o... PT Sweets Indonesia None None Jakarta Selatan Holding Companies, Patent Owners, and Trusts o... Indonesia Affinity Equity Partners Limited (Current Inve... NaN NaN NaN NaN NaN NaN 2390.000000 None None None NaN NaN None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
15622 YUPI 2008 PT Yupi Indo Jelly Gum Tbk (IDX:YUPI) 2025-03-21 Packaged Foods and Meats Producers 8608372 IDX Consumer Indonesia Gedung Mugi Griya Lt 4 www.yupindo.com PT Yupi Indo Jelly Gum Tbk manufactures and se... Public Company None Operating Subsidiary None No None None Yes No No None Fast-Moving Consumer Goods; Packaging Services... None 1995.000000 1995-07-06 00:00:00 NaN NaT None 2025-09-01 Packaged Foods and Meats Producers; Confection... Producers Packaged Foods and Meats Producers None Consumer Staples Food, Beverage and Tobacco Food Products Jakarta Selatan PT Sweets Indonesia None None 0.899000 Jakarta Selatan Holding Companies, Patent Owners, and Trusts o... PT Sweets Indonesia None None Jakarta Selatan Holding Companies, Patent Owners, and Trusts o... Indonesia Affinity Equity Partners Limited (Current Inve... NaN NaN NaN NaN NaN NaN 2390.000000 None None None NaN NaN None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

1105 rows × 91 columns

df_findat_final = df_findat_final[~df_findat_final['ticker'].isin(list_perusahaan_baru_ipo)]
print("Dataset Setelah Emiten Baru IPO Dihapus :\n")
df_findat_final
Dataset Setelah Emiten Baru IPO Dihapus :
variable ticker Year Entity Name IPO Date MM/dd/yyyy Primary Industry Entity ID Exchange 1st Level Primary Industry Country / Region Name Address 1 Web Address Business Description Company Type Investor Type Company Status Ownership Structure Activist Investor? Yes/No Institution Coverage Level Consolidation Status Has Current Financials? Yes/No Has Bank Regulatory Financials? Yes/No Has Third Party Financials? Yes/No Long Business Description Topic Tags Crunchbase Categories Year Established Date Established MM/dd/yyyy Year Incorporated Date Incorporated MM/dd/yyyy Month of Fiscal Year End Headcount As Of MM/dd/yyyy Industry Classification 2nd Level Primary Industry 3rd Level Primary Industry 4th Level Primary Industry Sector Industry Group Industry City Parent Company Name Parent Ticker Parent Exchange: Ticker Parent Percent Owned (%) Parent City Parent Industry Ultimate Parent Company Name Ultimate Parent Ticker Ultimate Parent Exchange: Ticker Ultimate Parent City Ultimate Parent Industry Ultimate Parent Country / Region All Investors Percent Owned - All Institutions (%) Shares Owned - All Institutions (actual) Market Value - All Institutions (Rp.B) Percent Owned - Insiders (%) Shares Owned - Insiders (actual) Market Value - Insiders (Rp.B) IPO Price (Rp.) Location Type Data Precision Data Year Total Liabilities (Rp.M) Total Assets (Rp.M) Current Ratio (x) Quick Ratio (x) Working Capital (Rp.M) Total Current Assets (Rp.M) Total Current Liabilities (Rp.M) Inventory (Rp.M) Prepaid Exp. (Rp.M) Long-term Debt (Rp.M) Short-term Borrowings (Rp.M) Current Portion of LT Debt & Leases (Rp.M) Total Debt (Rp.M) Total Equity (Rp.M) Net Property, Plant & Equipment (Rp.M) Cost Of Goods Sold (Rp.M) Total Revenue (Rp.M) Operating Income (Rp.M) EBITDA (Rp.M) EBIT (Rp.M) Net Income to Company (Rp.M) Cash from Ops. (Rp.M) Net Change in Cash (Rp.M) ECS Total Common Shares Outstanding (actual) Market Capitalization (Rp.B) Year Close Stock Price (Rp.) Total Capital (Rp.M) Cash & Short-term Investments (Rp.M) Net Intangibles (Rp.M)
17 AALI 2024 PT Astra Agro Lestari Tbk (IDX:AALI) NaT Agricultural Products and Services 4567075 IDX Consumer Indonesia Jl Puloayang Raya www.astra-agro.co.id PT Astra Agro Lestari Tbk engages in the crude... Public Company None Operating Subsidiary None No None None Yes No No PT Astra Agro Lestari Tbk is a prominent playe... None Agriculture, Business Intelligence, Communities 1988.000000 1988-10-03 00:00:00 NaN NaT Dec 2025-09-01 Agricultural Products and Services; Grain And ... Producers Agricultural Products and Services None Consumer Staples Food, Beverage and Tobacco Food Products Jakarta PT Astra International Tbk ASII IDX:ASII 0.796800 Jakarta Automobile Manufacturers Jardine Matheson Holdings Limited J36 SGX:J36 Hamilton Industrial Conglomerates Bermuda PT Astra International Tbk (Current Subsidiary... 2.100000 40473542.000000 315693.627600 NaN NaN NaN NaN None None None 5591163.000000 28793225.000000 2.605000 1.126000 5195985.000000 8433638.000000 3237653.000000 3981669.000000 NaN 1500000.000000 NaN NaN 3189537.000000 23202062.000000 17429693.000000 18358928.000000 21815035.000000 1788141.000000 3202738.000000 1788141.000000 1186783.000000 3379195.000000 1146504.000000 1924688333.000000 11933.067665 6200.000000 26391599.000000 3236012.000000 NaN
18 AALI 2023 PT Astra Agro Lestari Tbk (IDX:AALI) NaT Agricultural Products and Services 4567075 IDX Consumer Indonesia Jl Puloayang Raya www.astra-agro.co.id PT Astra Agro Lestari Tbk engages in the crude... Public Company None Operating Subsidiary None No None None Yes No No PT Astra Agro Lestari Tbk is a prominent playe... None Agriculture, Business Intelligence, Communities 1988.000000 1988-10-03 00:00:00 NaN NaT Dec 2025-09-01 Agricultural Products and Services; Grain And ... Producers Agricultural Products and Services None Consumer Staples Food, Beverage and Tobacco Food Products Jakarta PT Astra International Tbk ASII IDX:ASII 0.796800 Jakarta Automobile Manufacturers Jardine Matheson Holdings Limited J36 SGX:J36 Hamilton Industrial Conglomerates Bermuda PT Astra International Tbk (Current Subsidiary... 2.100000 40473542.000000 315693.627600 NaN NaN NaN NaN None None None 6280237.000000 28846243.000000 1.834000 0.765000 3236061.000000 7118202.000000 3882141.000000 3122454.000000 NaN 1689754.000000 NaN NaN 4005052.000000 22566006.000000 18105649.000000 17950652.000000 20745473.000000 1672092.000000 3007949.000000 1672092.000000 1088170.000000 2538738.000000 469892.000000 1924688333.000000 13520.935539 7025.000000 26571058.000000 2089508.000000 NaN
19 AALI 2022 PT Astra Agro Lestari Tbk (IDX:AALI) NaT Agricultural Products and Services 4567075 IDX Consumer Indonesia Jl Puloayang Raya www.astra-agro.co.id PT Astra Agro Lestari Tbk engages in the crude... Public Company None Operating Subsidiary None No None None Yes No No PT Astra Agro Lestari Tbk is a prominent playe... None Agriculture, Business Intelligence, Communities 1988.000000 1988-10-03 00:00:00 NaN NaT Dec 2025-09-01 Agricultural Products and Services; Grain And ... Producers Agricultural Products and Services None Consumer Staples Food, Beverage and Tobacco Food Products Jakarta PT Astra International Tbk ASII IDX:ASII 0.796800 Jakarta Automobile Manufacturers Jardine Matheson Holdings Limited J36 SGX:J36 Hamilton Industrial Conglomerates Bermuda PT Astra International Tbk (Current Subsidiary... 2.100000 40473542.000000 315693.627600 NaN NaN NaN NaN None None None 7006119.000000 29249340.000000 3.600000 1.227000 5337669.000000 7390608.000000 2052939.000000 3434768.000000 NaN 4048767.000000 NaN NaN 4053767.000000 22243221.000000 17996321.000000 18176356.000000 21828591.000000 2447192.000000 3710458.000000 2447192.000000 1792050.000000 1835397.000000 -2276406.000000 1924688333.000000 15445.623872 8025.000000 26296988.000000 1619616.000000 NaN
20 AALI 2021 PT Astra Agro Lestari Tbk (IDX:AALI) NaT Agricultural Products and Services 4567075 IDX Consumer Indonesia Jl Puloayang Raya www.astra-agro.co.id PT Astra Agro Lestari Tbk engages in the crude... Public Company None Operating Subsidiary None No None None Yes No No PT Astra Agro Lestari Tbk is a prominent playe... None Agriculture, Business Intelligence, Communities 1988.000000 1988-10-03 00:00:00 NaN NaT Dec 2025-09-01 Agricultural Products and Services; Grain And ... Producers Agricultural Products and Services None Consumer Staples Food, Beverage and Tobacco Food Products Jakarta PT Astra International Tbk ASII IDX:ASII 0.796800 Jakarta Automobile Manufacturers Jardine Matheson Holdings Limited J36 SGX:J36 Hamilton Industrial Conglomerates Bermuda PT Astra International Tbk (Current Subsidiary... 2.100000 40473542.000000 315693.627600 NaN NaN NaN NaN None None None 9228733.000000 30399906.000000 1.579000 0.752000 3453812.000000 9414208.000000 5960396.000000 3381506.000000 NaN 2253611.000000 NaN NaN 5818072.000000 21171173.000000 17781227.000000 19442136.000000 24322048.000000 3066334.000000 4314916.000000 3066334.000000 2067362.000000 4895119.000000 2917130.000000 1924688333.000000 18284.539164 9500.000000 26989245.000000 3896022.000000 NaN
21 AALI 2020 PT Astra Agro Lestari Tbk (IDX:AALI) NaT Agricultural Products and Services 4567075 IDX Consumer Indonesia Jl Puloayang Raya www.astra-agro.co.id PT Astra Agro Lestari Tbk engages in the crude... Public Company None Operating Subsidiary None No None None Yes No No PT Astra Agro Lestari Tbk is a prominent playe... None Agriculture, Business Intelligence, Communities 1988.000000 1988-10-03 00:00:00 NaN NaT Dec 2025-09-01 Agricultural Products and Services; Grain And ... Producers Agricultural Products and Services None Consumer Staples Food, Beverage and Tobacco Food Products Jakarta PT Astra International Tbk ASII IDX:ASII 0.796800 Jakarta Automobile Manufacturers Jardine Matheson Holdings Limited J36 SGX:J36 Hamilton Industrial Conglomerates Bermuda PT Astra International Tbk (Current Subsidiary... 2.100000 40473542.000000 315693.627600 NaN NaN NaN NaN None None None 8533437.000000 27781231.000000 3.313000 1.322000 4145384.000000 5937890.000000 1792506.000000 2505048.000000 NaN 5858300.000000 NaN NaN 5858300.000000 19247794.000000 17741481.000000 15809752.000000 18807043.000000 1708412.000000 3031177.000000 1708412.000000 893779.000000 2322164.000000 595526.000000 1924688333.000000 23721.783704 12325.000000 25106094.000000 978892.000000 NaN
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
15703 ZYRX 2012 PT Zyrexindo Mandiri Buana Tbk (IDX:ZYRX) 2021-03-26 Technology Hardware, Storage and Peripherals 5557676 IDX Technology, Media & Telecommunications Indonesia Jl. Daan Mogot No. 59 zyrex.com PT Zyrexindo Mandiri Buana Tbk assembles and s... Public Company None Operating None No Full Consolidated Yes No No PT Zyrexindo Mandiri Buana Tbk actively operat... Software Development; Sensors; Cloud Infrastru... None 1996.000000 None 1996.000000 1996-05-14 Dec 2025-08-01 Technology Hardware, Storage and Peripherals; ... Information Technology Technology Hardware and Equipment Technology Hardware, Storage and Peripherals Information Technology Technology Hardware and Equipment Technology Hardware, Storage and Peripherals Jakarta Barat None None None NA% None None None None None None None None None NaN NaN NaN 75.160000 1002076700.000000 178369.652600 250.000000 None None None NaN NaN None None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
15704 ZYRX 2011 PT Zyrexindo Mandiri Buana Tbk (IDX:ZYRX) 2021-03-26 Technology Hardware, Storage and Peripherals 5557676 IDX Technology, Media & Telecommunications Indonesia Jl. Daan Mogot No. 59 zyrex.com PT Zyrexindo Mandiri Buana Tbk assembles and s... Public Company None Operating None No Full Consolidated Yes No No PT Zyrexindo Mandiri Buana Tbk actively operat... Software Development; Sensors; Cloud Infrastru... None 1996.000000 None 1996.000000 1996-05-14 Dec 2025-08-01 Technology Hardware, Storage and Peripherals; ... Information Technology Technology Hardware and Equipment Technology Hardware, Storage and Peripherals Information Technology Technology Hardware and Equipment Technology Hardware, Storage and Peripherals Jakarta Barat None None None NA% None None None None None None None None None NaN NaN NaN 75.160000 1002076700.000000 178369.652600 250.000000 None None None NaN NaN None None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
15705 ZYRX 2010 PT Zyrexindo Mandiri Buana Tbk (IDX:ZYRX) 2021-03-26 Technology Hardware, Storage and Peripherals 5557676 IDX Technology, Media & Telecommunications Indonesia Jl. Daan Mogot No. 59 zyrex.com PT Zyrexindo Mandiri Buana Tbk assembles and s... Public Company None Operating None No Full Consolidated Yes No No PT Zyrexindo Mandiri Buana Tbk actively operat... Software Development; Sensors; Cloud Infrastru... None 1996.000000 None 1996.000000 1996-05-14 Dec 2025-08-01 Technology Hardware, Storage and Peripherals; ... Information Technology Technology Hardware and Equipment Technology Hardware, Storage and Peripherals Information Technology Technology Hardware and Equipment Technology Hardware, Storage and Peripherals Jakarta Barat None None None NA% None None None None None None None None None NaN NaN NaN 75.160000 1002076700.000000 178369.652600 250.000000 None None None NaN NaN None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
15706 ZYRX 2009 PT Zyrexindo Mandiri Buana Tbk (IDX:ZYRX) 2021-03-26 Technology Hardware, Storage and Peripherals 5557676 IDX Technology, Media & Telecommunications Indonesia Jl. Daan Mogot No. 59 zyrex.com PT Zyrexindo Mandiri Buana Tbk assembles and s... Public Company None Operating None No Full Consolidated Yes No No PT Zyrexindo Mandiri Buana Tbk actively operat... Software Development; Sensors; Cloud Infrastru... None 1996.000000 None 1996.000000 1996-05-14 Dec 2025-08-01 Technology Hardware, Storage and Peripherals; ... Information Technology Technology Hardware and Equipment Technology Hardware, Storage and Peripherals Information Technology Technology Hardware and Equipment Technology Hardware, Storage and Peripherals Jakarta Barat None None None NA% None None None None None None None None None NaN NaN NaN 75.160000 1002076700.000000 178369.652600 250.000000 None None None NaN NaN None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
15707 ZYRX 2008 PT Zyrexindo Mandiri Buana Tbk (IDX:ZYRX) 2021-03-26 Technology Hardware, Storage and Peripherals 5557676 IDX Technology, Media & Telecommunications Indonesia Jl. Daan Mogot No. 59 zyrex.com PT Zyrexindo Mandiri Buana Tbk assembles and s... Public Company None Operating None No Full Consolidated Yes No No PT Zyrexindo Mandiri Buana Tbk actively operat... Software Development; Sensors; Cloud Infrastru... None 1996.000000 None 1996.000000 1996-05-14 Dec 2025-08-01 Technology Hardware, Storage and Peripherals; ... Information Technology Technology Hardware and Equipment Technology Hardware, Storage and Peripherals Information Technology Technology Hardware and Equipment Technology Hardware, Storage and Peripherals Jakarta Barat None None None NA% None None None None None None None None None NaN NaN NaN 75.160000 1002076700.000000 178369.652600 250.000000 None None None NaN NaN None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

14501 rows × 91 columns

Hapus Baris Tahun Sebelum IPO

print('Cek perusahaan yang ada di df_findat_final tapi tidak ada di df_emiten_idx')
df_findat_final[~df_findat_final['ticker'].isin(df_emiten_idx['Kode'])]
Cek perusahaan yang ada di df_findat_final tapi tidak ada di df_emiten_idx
variable ticker Year Entity Name IPO Date MM/dd/yyyy Primary Industry Entity ID Exchange 1st Level Primary Industry Country / Region Name Address 1 Web Address Business Description Company Type Investor Type Company Status Ownership Structure Activist Investor? Yes/No Institution Coverage Level Consolidation Status Has Current Financials? Yes/No Has Bank Regulatory Financials? Yes/No Has Third Party Financials? Yes/No Long Business Description Topic Tags Crunchbase Categories Year Established Date Established MM/dd/yyyy Year Incorporated Date Incorporated MM/dd/yyyy Month of Fiscal Year End Headcount As Of MM/dd/yyyy Industry Classification 2nd Level Primary Industry 3rd Level Primary Industry 4th Level Primary Industry Sector Industry Group Industry City Parent Company Name Parent Ticker Parent Exchange: Ticker Parent Percent Owned (%) Parent City Parent Industry Ultimate Parent Company Name Ultimate Parent Ticker Ultimate Parent Exchange: Ticker Ultimate Parent City Ultimate Parent Industry Ultimate Parent Country / Region All Investors Percent Owned - All Institutions (%) Shares Owned - All Institutions (actual) Market Value - All Institutions (Rp.B) Percent Owned - Insiders (%) Shares Owned - Insiders (actual) Market Value - Insiders (Rp.B) IPO Price (Rp.) Location Type Data Precision Data Year Total Liabilities (Rp.M) Total Assets (Rp.M) Current Ratio (x) Quick Ratio (x) Working Capital (Rp.M) Total Current Assets (Rp.M) Total Current Liabilities (Rp.M) Inventory (Rp.M) Prepaid Exp. (Rp.M) Long-term Debt (Rp.M) Short-term Borrowings (Rp.M) Current Portion of LT Debt & Leases (Rp.M) Total Debt (Rp.M) Total Equity (Rp.M) Net Property, Plant & Equipment (Rp.M) Cost Of Goods Sold (Rp.M) Total Revenue (Rp.M) Operating Income (Rp.M) EBITDA (Rp.M) EBIT (Rp.M) Net Income to Company (Rp.M) Cash from Ops. (Rp.M) Net Change in Cash (Rp.M) ECS Total Common Shares Outstanding (actual) Market Capitalization (Rp.B) Year Close Stock Price (Rp.) Total Capital (Rp.M) Cash & Short-term Investments (Rp.M) Net Intangibles (Rp.M)
dict_perusahaan_tahun_ipo = dict(zip(df_emiten_idx['Kode'], df_emiten_idx['Tanggal Pencatatan'].dt.year))
print(dict_perusahaan_tahun_ipo)
{'AALI': 1997, 'ABBA': 2002, 'ABDA': 1989, 'ABMM': 2011, 'ACES': 2007, 'ACST': 2013, 'ADES': 1994, 'ADHI': 2004, 'ADMF': 2004, 'ADMG': 1993, 'ADRO': 2008, 'AGII': 2016, 'AGRO': 2003, 'AGRS': 2014, 'AHAP': 1990, 'AIMS': 2001, 'AISA': 1997, 'AKKU': 2004, 'AKPI': 1992, 'AKRA': 1994, 'AKSI': 2001, 'ALDO': 2011, 'ALKA': 1990, 'ALMI': 1997, 'ALTO': 2012, 'AMAG': 2005, 'AMFG': 1995, 'AMIN': 2015, 'AMRT': 2009, 'ANJT': 2013, 'ANTM': 1997, 'APEX': 2013, 'APIC': 2002, 'APII': 2013, 'APLI': 2000, 'APLN': 2010, 'ARGO': 1991, 'ARII': 2011, 'ARNA': 2001, 'ARTA': 2002, 'ARTI': 2003, 'ARTO': 2016, 'ASBI': 1989, 'ASDM': 1989, 'ASGR': 1989, 'ASII': 1990, 'ASJT': 2003, 'ASMI': 2014, 'ASRI': 2007, 'ASRM': 1990, 'ASSA': 2012, 'ATIC': 2015, 'AUTO': 1998, 'BABP': 2002, 'BACA': 2007, 'BAJA': 2011, 'BALI': 2014, 'BAPA': 2008, 'BATA': 1982, 'BAYU': 1989, 'BBCA': 2000, 'BBHI': 2015, 'BBKP': 2006, 'BBLD': 1990, 'BBMD': 2013, 'BBNI': 1996, 'BBRI': 2003, 'BBRM': 2013, 'BBTN': 2009, 'BBYB': 2015, 'BCAP': 2001, 'BCIC': 1997, 'BCIP': 2009, 'BDMN': 1989, 'BEKS': 2001, 'BEST': 2012, 'BFIN': 1990, 'BGTG': 2016, 'BHIT': 1997, 'BIKA': 2015, 'BIMA': 1994, 'BINA': 2014, 'BIPI': 2010, 'BIPP': 1995, 'BIRD': 2014, 'BISI': 2007, 'BJBR': 2010, 'BJTM': 2012, 'BKDP': 2007, 'BKSL': 1997, 'BKSW': 2002, 'BLTA': 1990, 'BLTZ': 2014, 'BMAS': 2013, 'BMRI': 2003, 'BMSR': 1999, 'BMTR': 1995, 'BNBA': 2006, 'BNBR': 1989, 'BNGA': 1989, 'BNII': 1989, 'BNLI': 1990, 'BOLT': 2015, 'BPFI': 2009, 'BPII': 2014, 'BRAM': 1990, 'BRMS': 2010, 'BRNA': 1989, 'BRPT': 1993, 'BSDE': 2008, 'BSIM': 2010, 'BSSR': 2012, 'BSWD': 2002, 'BTEK': 2004, 'BTEL': 2006, 'BTON': 2001, 'BTPN': 2008, 'BUDI': 1995, 'BUKK': 2015, 'BULL': 2011, 'BUMI': 1990, 'BUVA': 2010, 'BVIC': 1999, 'BWPT': 2009, 'BYAN': 2008, 'CANI': 2014, 'CASS': 2011, 'CEKA': 1996, 'CENT': 2001, 'CFIN': 1990, 'CINT': 2014, 'CITA': 2002, 'CLPI': 2001, 'CMNP': 1995, 'CMPP': 1994, 'CNKO': 2001, 'CNTX': 1979, 'COWL': 2007, 'CPIN': 1991, 'CPRO': 2006, 'CSAP': 2007, 'CTBN': 1989, 'CTRA': 1994, 'CTTH': 1996, 'DART': 1990, 'DEFI': 2001, 'DEWA': 2007, 'DGIK': 2007, 'DILD': 1991, 'DKFT': 1997, 'DLTA': 1984, 'DMAS': 2015, 'DNAR': 2014, 'DNET': 2000, 'DOID': 2001, 'DPNS': 1990, 'DSFI': 2000, 'DSNG': 2013, 'DSSA': 2009, 'DUTI': 1994, 'DVLA': 1994, 'DYAN': 2013, 'ECII': 2013, 'EKAD': 1990, 'ELSA': 2008, 'ELTY': 1995, 'EMDE': 2011, 'EMTK': 2010, 'ENRG': 2004, 'EPMT': 1994, 'ERAA': 2011, 'ERTX': 1990, 'ESSA': 2012, 'ESTI': 1992, 'ETWA': 1997, 'EXCL': 2005, 'FAST': 1993, 'FASW': 1994, 'FISH': 2002, 'FMII': 2000, 'FORU': 2002, 'FPNI': 2002, 'GAMA': 2012, 'GDST': 2009, 'GDYR': 1980, 'GEMA': 2002, 'GEMS': 2011, 'GGRM': 1990, 'GIAA': 2011, 'GJTL': 1990, 'GLOB': 2012, 'GMTD': 2000, 'GOLD': 2010, 'GOLL': 2014, 'GPRA': 2007, 'GSMF': 1989, 'GTBO': 2009, 'GWSA': 2011, 'GZCO': 2008, 'HADE': 2004, 'HDFA': 2011, 'HERO': 1989, 'HEXA': 1995, 'HITS': 1997, 'HMSP': 1990, 'HOME': 2008, 'HOTL': 2013, 'HRUM': 2010, 'IATA': 2006, 'IBFN': 2014, 'IBST': 2012, 'ICBP': 2010, 'ICON': 2005, 'IGAR': 1990, 'IIKP': 2002, 'IKAI': 1997, 'IKBI': 1991, 'IMAS': 1993, 'IMJS': 2013, 'IMPC': 2014, 'INAF': 2001, 'INAI': 1994, 'INCI': 1990, 'INCO': 1990, 'INDF': 1994, 'INDR': 1990, 'INDS': 1990, 'INDX': 2001, 'INDY': 2008, 'INKP': 1990, 'INPC': 1990, 'INPP': 2004, 'INRU': 1990, 'INTA': 1993, 'INTD': 1989, 'INTP': 1989, 'IPOL': 2010, 'ISAT': 1994, 'ISSP': 2013, 'ITMA': 1990, 'ITMG': 2007, 'JAWA': 2011, 'JECC': 1992, 'JIHD': 1984, 'JKON': 2007, 'JPFA': 1989, 'JRPT': 1994, 'JSMR': 2007, 'JSPT': 1998, 'JTPE': 2002, 'KAEF': 2001, 'KARW': 1994, 'KBLI': 1992, 'KBLM': 1992, 'KBLV': 2000, 'KBRI': 2008, 'KDSI': 1996, 'KIAS': 1994, 'KICI': 1993, 'KIJA': 1995, 'KKGI': 1991, 'KLBF': 1991, 'KOBX': 2012, 'KOIN': 2008, 'KONI': 1995, 'KOPI': 2015, 'KPIG': 2000, 'KRAS': 2010, 'KREN': 2002, 'LAPD': 2001, 'LCGP': 2007, 'LEAD': 2013, 'LINK': 2014, 'LION': 1993, 'LMAS': 2001, 'LMPI': 1994, 'LMSH': 1990, 'LPCK': 1997, 'LPGI': 1997, 'LPIN': 1990, 'LPKR': 1996, 'LPLI': 1989, 'LPPF': 1989, 'LPPS': 1994, 'LRNA': 2014, 'LSIP': 1996, 'LTLS': 1997, 'MAGP': 2013, 'MAIN': 2006, 'MAPI': 2004, 'MASA': 2005, 'MAYA': 1997, 'MBAP': 2014, 'MBSS': 2011, 'MBTO': 2011, 'MCOR': 2007, 'MDIA': 2014, 'MDKA': 2015, 'MDLN': 1993, 'MDRN': 1991, 'MEDC': 1994, 'MEGA': 2000, 'MERK': 1981, 'META': 2001, 'MFIN': 2005, 'MFMI': 2010, 'MGNA': 2014, 'MICE': 2005, 'MIDI': 2010, 'MIKA': 2015, 'MIRA': 1997, 'MITI': 1997, 'MKPI': 2009, 'MLBI': 1981, 'MLIA': 1994, 'MLPL': 1989, 'MLPT': 2013, 'MMLP': 2015, 'MNCN': 2007, 'MPMX': 2013, 'MPPA': 1992, 'MRAT': 1995, 'MREI': 1989, 'MSKY': 2012, 'MTDL': 1990, 'MTFN': 1990, 'MTLA': 2011, 'MTSM': 1992, 'MYOH': 2000, 'MYOR': 1990, 'MYTX': 1989, 'NELY': 2012, 'NIKL': 2009, 'NIRO': 2012, 'NISP': 1994, 'NOBU': 2013, 'NRCA': 2013, 'OCAP': 2003, 'OKAS': 2006, 'OMRE': 1994, 'PADI': 2012, 'PALM': 2012, 'PANR': 2001, 'PANS': 2000, 'PBRX': 1990, 'PDES': 2008, 'PEGE': 2005, 'PGAS': 2003, 'PGLI': 2000, 'PICO': 1996, 'PJAA': 2004, 'PKPK': 2007, 'PLAS': 2001, 'PLIN': 1992, 'PNBN': 1982, 'PNBS': 2014, 'PNIN': 1983, 'PNLF': 1983, 'PNSE': 1990, 'POLY': 1991, 'POOL': 1991, 'PPRO': 2015, 'PSAB': 2003, 'PSDN': 1994, 'PSKT': 1995, 'PTBA': 2002, 'PTIS': 2011, 'PTPP': 2010, 'PTRO': 1990, 'PTSN': 2007, 'PTSP': 1994, 'PUDP': 1994, 'PWON': 1989, 'PYFA': 2001, 'RAJA': 2006, 'RALS': 1996, 'RANC': 2012, 'RBMS': 1997, 'RDTX': 1990, 'RELI': 2005, 'RICY': 1998, 'RIGS': 1990, 'RIMO': 2000, 'RODA': 2001, 'ROTI': 2010, 'RUIS': 2006, 'SAFE': 1994, 'SAME': 2013, 'SCCO': 1982, 'SCMA': 2002, 'SCPI': 1990, 'SDMU': 2011, 'SDPC': 1990, 'SDRA': 2006, 'SGRO': 2007, 'SHID': 1990, 'SIDO': 2013, 'SILO': 2013, 'SIMA': 1994, 'SIMP': 2011, 'SIPD': 1996, 'SKBM': 2012, 'SKLT': 1993, 'SKYB': 2010, 'SMAR': 1992, 'SMBR': 2013, 'SMCB': 1977, 'SMDM': 1995, 'SMDR': 1999, 'SMGR': 1991, 'SMMA': 1995, 'SMMT': 1997, 'SMRA': 1990, 'SMRU': 2011, 'SMSM': 1996, 'SOCI': 2014, 'SONA': 1992, 'SPMA': 1994, 'SQMI': 2004, 'SRAJ': 2011, 'SRIL': 2013, 'SRSN': 1993, 'SRTG': 2013, 'SSIA': 1997, 'SSMS': 2013, 'SSTM': 1997, 'STAR': 2011, 'STTP': 1996, 'SUGI': 2002, 'SULI': 1994, 'SUPR': 2011, 'TALF': 2014, 'TARA': 2014, 'TAXI': 2012, 'TBIG': 2010, 'TBLA': 2000, 'TBMS': 1990, 'TCID': 1993, 'TELE': 2012, 'TFCO': 1980, 'TGKA': 1990, 'TIFA': 2011, 'TINS': 1995, 'TIRA': 1993, 'TIRT': 1999, 'TKIM': 1990, 'TLKM': 1995, 'TMAS': 2003, 'TMPO': 2001, 'TOBA': 2012, 'TOTL': 2006, 'TOTO': 1990, 'TOWR': 2010, 'TPIA': 2008, 'TPMA': 2013, 'TRAM': 2008, 'TRIL': 2008, 'TRIM': 2000, 'TRIO': 2009, 'TRIS': 2012, 'TRST': 1990, 'TRUS': 2002, 'TSPC': 1994, 'ULTJ': 1990, 'UNIC': 1989, 'UNIT': 2002, 'UNSP': 1990, 'UNTR': 1989, 'UNVR': 1982, 'VICO': 2013, 'VINS': 2015, 'VIVA': 2011, 'VOKS': 1990, 'VRNA': 2008, 'WAPO': 2001, 'WEHA': 2007, 'WICO': 1994, 'WIIM': 2012, 'WIKA': 2007, 'WINS': 2010, 'WOMF': 2004, 'WSKT': 2012, 'WTON': 2014, 'YPAS': 2008, 'YULE': 2004, 'ZBRA': 1991, 'SHIP': 2016, 'CASA': 2016, 'DAYA': 2016, 'DPUM': 2015, 'IDPR': 2015, 'JGLE': 2016, 'KINO': 2015, 'MARI': 2016, 'MKNT': 2015, 'MTRA': 2016, 'OASA': 2016, 'POWR': 2016, 'INCF': 2016, 'WSBP': 2016, 'PBSA': 2016, 'PRDA': 2016, 'BOGA': 2016, 'BRIS': 2018, 'PORT': 2017, 'CARS': 2017, 'MINA': 2017, 'CLEO': 2017, 'TAMU': 2017, 'CSIS': 2017, 'TGRA': 2017, 'FIRE': 2017, 'TOPS': 2017, 'KMTR': 2017, 'ARMY': 2017, 'MAPB': 2017, 'WOOD': 2017, 'HRTA': 2017, 'MABA': 2017, 'HOKI': 2017, 'MPOW': 2017, 'MARK': 2017, 'NASA': 2017, 'MDKI': 2017, 'BELL': 2017, 'KIOS': 2017, 'GMFI': 2017, 'MTWI': 2017, 'ZINC': 2017, 'MCAS': 2017, 'PPRE': 2017, 'WEGE': 2017, 'PSSI': 2017, 'MORA': 2022, 'DWGL': 2017, 'PBID': 2017, 'JMAS': 2017, 'CAMP': 2017, 'IPCM': 2017, 'PCAR': 2017, 'LCKM': 2018, 'BOSS': 2018, 'HELI': 2018, 'JSKY': 2018, 'INPS': 2018, 'GHON': 2018, 'TDPM': 2018, 'DFAM': 2018, 'NICK': 2018, 'BTPS': 2018, 'SPTO': 2018, 'PRIM': 2018, 'HEAL': 2018, 'TRUK': 2018, 'PZZA': 2018, 'TUGU': 2018, 'MSIN': 2018, 'SWAT': 2018, 'TNCA': 2018, 'MAPA': 2018, 'TCPI': 2018, 'IPCC': 2018, 'RISE': 2018, 'BPTR': 2018, 'POLL': 2018, 'NFCX': 2018, 'MGRO': 2018, 'NUSA': 2018, 'FILM': 2018, 'ANDI': 2018, 'LAND': 2018, 'MOLI': 2018, 'PANI': 2018, 'DIGI': 2018, 'CITY': 2018, 'SAPX': 2018, 'SURE': 2018, 'HKMU': 2018, 'MPRO': 2018, 'DUCK': 2018, 'GOOD': 2018, 'SKRN': 2018, 'YELO': 2018, 'CAKK': 2018, 'SATU': 2018, 'SOSS': 2018, 'DEAL': 2018, 'POLA': 2018, 'DIVA': 2018, 'LUCK': 2018, 'URBN': 2018, 'SOTS': 2018, 'ZONE': 2018, 'PEHA': 2018, 'FOOD': 2019, 'BEEF': 2019, 'POLI': 2019, 'CLAY': 2019, 'NATO': 2019, 'JAYA': 2019, 'COCO': 2019, 'MTPS': 2019, 'CPRI': 2019, 'HRME': 2019, 'POSA': 2019, 'JAST': 2019, 'FITT': 2019, 'BOLA': 2019, 'CCSI': 2019, 'SFAN': 2019, 'POLU': 2019, 'KJEN': 2019, 'KAYU': 2019, 'ITIC': 2019, 'PAMG': 2019, 'IPTV': 2019, 'BLUE': 2019, 'ENVY': 2019, 'EAST': 2019, 'LIFE': 2019, 'FUJI': 2019, 'KOTA': 2019, 'INOV': 2019, 'ARKA': 2019, 'SMKL': 2019, 'HDIT': 2019, 'KEEN': 2019, 'BAPI': 2019, 'TFAS': 2019, 'GGRP': 2019, 'OPMS': 2019, 'NZIA': 2019, 'SLIS': 2019, 'PURE': 2019, 'IRRA': 2019, 'DMMX': 2019, 'SINI': 2019, 'WOWS': 2019, 'ESIP': 2019, 'TEBE': 2019, 'KEJU': 2019, 'PSGO': 2019, 'AGAR': 2019, 'IFSH': 2019, 'REAL': 2019, 'IFII': 2019, 'PMJS': 2019, 'UCID': 2019, 'GLVA': 2019, 'PGJO': 2020, 'AMAR': 2020, 'CSRA': 2020, 'INDO': 2020, 'AMOR': 2020, 'TRIN': 2020, 'DMND': 2020, 'PURA': 2020, 'PTPW': 2020, 'TAMA': 2020, 'IKAN': 2020, 'SAMF': 2020, 'SBAT': 2020, 'KBAG': 2020, 'CBMF': 2020, 'RONY': 2020, 'CSMI': 2020, 'BBSS': 2020, 'BHAT': 2020, 'CASH': 2020, 'TECH': 2020, 'EPAC': 2020, 'UANG': 2020, 'PGUN': 2020, 'SOFA': 2020, 'PPGL': 2020, 'TOYS': 2020, 'SGER': 2020, 'TRJA': 2020, 'PNGO': 2020, 'SCNP': 2020, 'BBSI': 2020, 'KMDS': 2020, 'PURI': 2020, 'SOHO': 2020, 'HOMI': 2020, 'ROCK': 2020, 'ENZO': 2020, 'PLAN': 2020, 'PTDU': 2020, 'ATAP': 2020, 'VICI': 2020, 'PMMP': 2020, 'WIFI': 2020, 'FAPA': 2021, 'DCII': 2021, 'KETR': 2021, 'DGNS': 2021, 'UFOE': 2021, 'BANK': 2021, 'WMUU': 2021, 'EDGE': 2021, 'UNIQ': 2021, 'BEBS': 2021, 'SNLK': 2021, 'ZYRX': 2021, 'LFLO': 2021, 'FIMP': 2021, 'TAPG': 2021, 'NPGF': 2021, 'LUCY': 2021, 'ADCP': 2021, 'HOPE': 2021, 'MGLV': 2021, 'TRUE': 2021, 'LABA': 2021, 'ARCI': 2021, 'IPAC': 2021, 'MASB': 2021, 'BMHS': 2021, 'FLMC': 2021, 'NICL': 2021, 'UVCR': 2021, 'BUKA': 2021, 'HAIS': 2021, 'OILS': 2021, 'GPSO': 2021, 'MCOL': 2021, 'RSGK': 2021, 'RUNS': 2021, 'SBMA': 2021, 'CMNT': 2021, 'GTSI': 2021, 'IDEA': 2021, 'KUAS': 2021, 'BOBA': 2021, 'MTEL': 2021, 'DEPO': 2021, 'BINO': 2021, 'CMRY': 2021, 'WGSH': 2021, 'TAYS': 2021, 'WMPP': 2021, 'RMKE': 2021, 'OBMD': 2021, 'AVIA': 2021, 'IPPE': 2021, 'NASI': 2021, 'BSML': 2021, 'DRMA': 2021, 'ADMR': 2022, 'SEMA': 2022, 'ASLC': 2022, 'NETV': 2022, 'BAUT': 2022, 'ENAK': 2022, 'NTBK': 2022, 'SMKM': 2022, 'STAA': 2022, 'NANO': 2022, 'BIKE': 2022, 'WIRG': 2022, 'SICO': 2022, 'GOTO': 2022, 'TLDN': 2022, 'MTMH': 2022, 'WINR': 2022, 'IBOS': 2022, 'OLIV': 2022, 'ASHA': 2022, 'SWID': 2022, 'TRGU': 2022, 'ARKO': 2022, 'CHEM': 2022, 'DEWI': 2022, 'AXIO': 2022, 'KRYA': 2022, 'HATM': 2022, 'RCCC': 2022, 'GULA': 2022, 'JARR': 2022, 'AMMS': 2022, 'RAFI': 2022, 'KKES': 2022, 'ELPI': 2022, 'EURO': 2022, 'KLIN': 2022, 'TOOL': 2022, 'BUAH': 2022, 'CRAB': 2022, 'MEDS': 2022, 'COAL': 2022, 'PRAY': 2022, 'CBUT': 2022, 'BELI': 2022, 'MKTR': 2022, 'OMED': 2022, 'BSBK': 2022, 'PDPP': 2022, 'KDTN': 2022, 'ZATA': 2022, 'NINE': 2022, 'MMIX': 2022, 'PADA': 2022, 'ISAP': 2022, 'VTNY': 2022, 'SOUL': 2023, 'ELIT': 2023, 'BEER': 2023, 'CBPE': 2023, 'SUNI': 2023, 'CBRE': 2023, 'WINE': 2023, 'BMBL': 2023, 'PEVE': 2023, 'LAJU': 2023, 'FWCT': 2023, 'NAYZ': 2023, 'IRSX': 2023, 'PACK': 2023, 'VAST': 2023, 'CHIP': 2023, 'HALO': 2023, 'KING': 2023, 'PGEO': 2023, 'FUTR': 2023, 'HILL': 2023, 'BDKR': 2023, 'PTMP': 2023, 'SAGE': 2023, 'TRON': 2023, 'CUAN': 2023, 'NSSS': 2023, 'GTRA': 2023, 'HAJJ': 2023, 'PIPA': 2023, 'NCKL': 2023, 'MENN': 2023, 'AWAN': 2023, 'MBMA': 2023, 'RAAM': 2023, 'DOOH': 2023, 'JATI': 2023, 'TYRE': 2023, 'MPXL': 2023, 'SMIL': 2023, 'KLAS': 2023, 'MAXI': 2023, 'VKTR': 2023, 'RELF': 2023, 'AMMN': 2023, 'CRSN': 2023, 'GRPM': 2023, 'WIDI': 2023, 'TGUK': 2023, 'INET': 2023, 'MAHA': 2023, 'RMKO': 2023, 'CNMA': 2023, 'FOLK': 2023, 'HBAT': 2023, 'GRIA': 2023, 'PPRI': 2023, 'ERAL': 2023, 'CYBR': 2023, 'MUTU': 2023, 'LMAX': 2023, 'HUMI': 2023, 'MSIE': 2023, 'RSCH': 2023, 'BABY': 2023, 'AEGS': 2023, 'IOTF': 2023, 'KOCI': 2023, 'PTPS': 2023, 'BREN': 2023, 'STRK': 2023, 'KOKA': 2023, 'LOPI': 2023, 'UDNG': 2023, 'RGAS': 2023, 'MSTI': 2023, 'IKPM': 2023, 'AYAM': 2023, 'SURI': 2023, 'ASLI': 2024, 'CGAS': 2024, 'NICE': 2024, 'MSJA': 2024, 'SMLE': 2024, 'ACRO': 2024, 'MANG': 2024, 'GRPH': 2024, 'SMGA': 2024, 'UNTD': 2024, 'TOSK': 2024, 'MPIX': 2024, 'ALII': 2024, 'MKAP': 2024, 'MEJA': 2024, 'LIVE': 2024, 'HYGN': 2024, 'BAIK': 2024, 'VISI': 2024, 'AREA': 2024, 'MHKI': 2024, 'ATLA': 2024, 'DATA': 2024, 'SOLA': 2024, 'BATR': 2024, 'SPRE': 2024, 'PART': 2024, 'GOLF': 2024, 'ISEA': 2024, 'BLES': 2024, 'GUNA': 2024, 'LABS': 2024, 'DOSS': 2024, 'NEST': 2024, 'PTMR': 2024, 'VERN': 2024, 'DAAZ': 2024, 'BOAT': 2024, 'NAIK': 2024, 'AADI': 2024, 'MDIY': 2024, 'KSIX': 2025, 'RATU': 2025, 'YOII': 2025, 'HGII': 2025, 'BRRC': 2025, 'DGWG': 2025, 'CBDK': 2025, 'OBAT': 2025, 'MINE': 2025, 'ASPR': 2025, 'PSAT': 2025, 'COIN': 2025, 'CDIA': 2025, 'BLOG': 2025, 'MERI': 2025, 'CHEK': 2025, 'PMUI': 2025, 'EMAS': 2025, 'KAQI': 2025, 'YUPI': 2025, 'FORE': 2025, 'MDLA': 2025, 'DKHH': 2025, 'AYLS': 2020, 'DADA': 2020, 'ASPI': 2020, 'ESTA': 2020, 'BESS': 2020, 'AMAN': 2020, 'CARE': 2020}
indices_to_drop = []

for ticker, ipo_year in dict_perusahaan_tahun_ipo.items():
    # Find rows where the ticker matches and the year is before the IPO year
    rows_before_ipo = df_findat_final[
        (df_findat_final['ticker'] == ticker) &
        (df_findat_final['Year'] <= ipo_year)
    ]
    indices_to_drop.extend(rows_before_ipo.index.tolist())
# Baris yang akan dihapus
df_findat_final.loc[indices_to_drop]
variable ticker Year Entity Name IPO Date MM/dd/yyyy Primary Industry Entity ID Exchange 1st Level Primary Industry Country / Region Name Address 1 Web Address Business Description Company Type Investor Type Company Status Ownership Structure Activist Investor? Yes/No Institution Coverage Level Consolidation Status Has Current Financials? Yes/No Has Bank Regulatory Financials? Yes/No Has Third Party Financials? Yes/No Long Business Description Topic Tags Crunchbase Categories Year Established Date Established MM/dd/yyyy Year Incorporated Date Incorporated MM/dd/yyyy Month of Fiscal Year End Headcount As Of MM/dd/yyyy Industry Classification 2nd Level Primary Industry 3rd Level Primary Industry 4th Level Primary Industry Sector Industry Group Industry City Parent Company Name Parent Ticker Parent Exchange: Ticker Parent Percent Owned (%) Parent City Parent Industry Ultimate Parent Company Name Ultimate Parent Ticker Ultimate Parent Exchange: Ticker Ultimate Parent City Ultimate Parent Industry Ultimate Parent Country / Region All Investors Percent Owned - All Institutions (%) Shares Owned - All Institutions (actual) Market Value - All Institutions (Rp.B) Percent Owned - Insiders (%) Shares Owned - Insiders (actual) Market Value - Insiders (Rp.B) IPO Price (Rp.) Location Type Data Precision Data Year Total Liabilities (Rp.M) Total Assets (Rp.M) Current Ratio (x) Quick Ratio (x) Working Capital (Rp.M) Total Current Assets (Rp.M) Total Current Liabilities (Rp.M) Inventory (Rp.M) Prepaid Exp. (Rp.M) Long-term Debt (Rp.M) Short-term Borrowings (Rp.M) Current Portion of LT Debt & Leases (Rp.M) Total Debt (Rp.M) Total Equity (Rp.M) Net Property, Plant & Equipment (Rp.M) Cost Of Goods Sold (Rp.M) Total Revenue (Rp.M) Operating Income (Rp.M) EBITDA (Rp.M) EBIT (Rp.M) Net Income to Company (Rp.M) Cash from Ops. (Rp.M) Net Change in Cash (Rp.M) ECS Total Common Shares Outstanding (actual) Market Capitalization (Rp.B) Year Close Stock Price (Rp.) Total Capital (Rp.M) Cash & Short-term Investments (Rp.M) Net Intangibles (Rp.M)
81 ABMM 2011 PT ABM Investama Tbk (IDX:ABMM) 2011-12-05 Coal and Consumable Fuels 4980353 IDX Energy and Utilities Indonesia Tiara Marga Trakindo I building www.abm-investama.com PT ABM Investama Tbk, together with its subsid... Public Company None Operating Subsidiary None No None None Yes No No PT ABM Investama Tbk operating in the sectors ... Clean Energy; Freight Service; Marine Transpor... Energy, Precious Metals 2006.000000 2006-06-01 00:00:00 NaN NaT Dec 2025-09-01 Coal and Consumable Fuels Oil, Gas and Coal Coal and Consumable Fuels None Energy Energy Oil, Gas and Consumable Fuels Jakarta PT Tiara Marga Trakindo None None 0.535500 Jakarta Industrial Conglomerates PT Tiara Marga Trakindo None None Jakarta Industrial Conglomerates Indonesia PT Tiara Marga Trakindo (Current Subsidiary or... 0.830000 22926501.000000 68779.503000 7.340000 202136500.000000 606409.500000 3750.000000 None None None 7019323.859661 10077243.517333 1.362000 1.088000 1134634.650895 4270489.900468 3135855.249573 414473.810135 26318.699181 2832092.967225 494015.259308 NaN 4929981.462200 3057919.657672 4881836.576369 5237512.675191 6607997.885733 706361.557448 1250603.533610 706361.557448 484431.520834 53032.416782 1190873.399695 2753165000.000000 10530.856125 3825.000000 7987901.119872 1669222.334305 NaN
82 ABMM 2010 PT ABM Investama Tbk (IDX:ABMM) 2011-12-05 Coal and Consumable Fuels 4980353 IDX Energy and Utilities Indonesia Tiara Marga Trakindo I building www.abm-investama.com PT ABM Investama Tbk, together with its subsid... Public Company None Operating Subsidiary None No None None Yes No No PT ABM Investama Tbk operating in the sectors ... Clean Energy; Freight Service; Marine Transpor... Energy, Precious Metals 2006.000000 2006-06-01 00:00:00 NaN NaT Dec 2025-09-01 Coal and Consumable Fuels Oil, Gas and Coal Coal and Consumable Fuels None Energy Energy Oil, Gas and Consumable Fuels Jakarta PT Tiara Marga Trakindo None None 0.535500 Jakarta Industrial Conglomerates PT Tiara Marga Trakindo None None Jakarta Industrial Conglomerates Indonesia PT Tiara Marga Trakindo (Current Subsidiary or... 0.830000 22926501.000000 68779.503000 7.340000 202136500.000000 606409.500000 3750.000000 None None None 3763830.000000 4890266.000000 0.893000 0.700000 -252891.000000 2106194.000000 2359085.000000 301356.000000 11526.000000 841659.000000 292208.000000 NaN 1924066.000000 1126436.000000 2172994.000000 3789687.000000 4486419.000000 271266.000000 705326.000000 271266.000000 127376.000000 612476.000000 -181536.000000 825760000.000000 NaN NaN 3050502.000000 433039.000000 NaN
83 ABMM 2009 PT ABM Investama Tbk (IDX:ABMM) 2011-12-05 Coal and Consumable Fuels 4980353 IDX Energy and Utilities Indonesia Tiara Marga Trakindo I building www.abm-investama.com PT ABM Investama Tbk, together with its subsid... Public Company None Operating Subsidiary None No None None Yes No No PT ABM Investama Tbk operating in the sectors ... Clean Energy; Freight Service; Marine Transpor... Energy, Precious Metals 2006.000000 2006-06-01 00:00:00 NaN NaT Dec 2025-09-01 Coal and Consumable Fuels Oil, Gas and Coal Coal and Consumable Fuels None Energy Energy Oil, Gas and Consumable Fuels Jakarta PT Tiara Marga Trakindo None None 0.535500 Jakarta Industrial Conglomerates PT Tiara Marga Trakindo None None Jakarta Industrial Conglomerates Indonesia PT Tiara Marga Trakindo (Current Subsidiary or... 0.830000 22926501.000000 68779.503000 7.340000 202136500.000000 606409.500000 3750.000000 None None None 3099452.000000 4199977.000000 1.113000 0.879000 203475.000000 2001334.000000 1797859.000000 233936.000000 NaN NaN NaN NaN 0.000000 1100525.000000 1532725.000000 3455803.000000 3926320.000000 491429.000000 NaN 491429.000000 -11346.000000 678998.000000 455151.000000 826000000.000000 NaN NaN 1100525.000000 614575.000000 NaN
84 ABMM 2008 PT ABM Investama Tbk (IDX:ABMM) 2011-12-05 Coal and Consumable Fuels 4980353 IDX Energy and Utilities Indonesia Tiara Marga Trakindo I building www.abm-investama.com PT ABM Investama Tbk, together with its subsid... Public Company None Operating Subsidiary None No None None Yes No No PT ABM Investama Tbk operating in the sectors ... Clean Energy; Freight Service; Marine Transpor... Energy, Precious Metals 2006.000000 2006-06-01 00:00:00 NaN NaT Dec 2025-09-01 Coal and Consumable Fuels Oil, Gas and Coal Coal and Consumable Fuels None Energy Energy Oil, Gas and Consumable Fuels Jakarta PT Tiara Marga Trakindo None None 0.535500 Jakarta Industrial Conglomerates PT Tiara Marga Trakindo None None Jakarta Industrial Conglomerates Indonesia PT Tiara Marga Trakindo (Current Subsidiary or... 0.830000 22926501.000000 68779.503000 7.340000 202136500.000000 606409.500000 3750.000000 None None None 4360950.000000 3549129.000000 0.592000 0.430000 -953832.000000 1384649.000000 2338481.000000 241667.000000 NaN NaN NaN NaN 0.000000 -811821.000000 1469802.000000 3114382.000000 3235172.000000 -469841.000000 NaN -469841.000000 -3913.000000 -4564.000000 7861.000000 NaN NaN NaN -811821.000000 159424.000000 NaN
130 ACST 2013 PT Acset Indonusa Tbk (IDX:ACST) 2013-06-20 Construction and Engineering 4990054 IDX Industrials Indonesia ACSET Building www.acset.co PT Acset Indonusa Tbk provides construction an... Public Company None Operating Subsidiary None No None None Yes No No PT Acset Indonusa Tbk, an integrated construct... None Civil Engineering, Construction 1995.000000 1995-01-10 00:00:00 1995.000000 1995-01-10 Dec 2025-09-01 Construction and Engineering; Commercial Const... Capital Goods Construction and Engineering None Industrials Capital Goods Construction and Engineering Jakarta PT Karya Supra Perkasa None None 0.501000 None Unclassified Jardine Matheson Holdings Limited J36 SGX:J36 Hamilton Industrial Conglomerates Bermuda PT Karya Supra Perkasa (Current Subsidiary or ... 0.110000 20245000.000000 2753.320000 NaN NaN NaN 2500.000000 None None None 536559.738000 1298358.203000 1.484000 0.922000 346109.300000 1061422.848000 715313.548000 237777.966000 6015.411000 16885.953000 15961.846000 NaN 118266.063000 560442.562000 220839.892000 818537.622000 1014502.030000 146399.359000 194155.134000 146399.359000 99215.342000 -112217.697000 -19912.184000 500000000.000000 995.000000 1990.000000 678708.625000 48718.694000 NaN
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
3293 CARE 2012 PT Metro Healthcare Indonesia Tbk (IDX:CARE) 2020-03-11 Health Care Facilities 19890578 IDX Health Care Indonesia Jl. Raya Serang Km 16,8 www.metrohealthcareindonesia.co.id PT Metro Healthcare Indonesia Tbk owns, operat... Public Company None Operating None No None None Yes No No PT Metro Healthcare Indonesia Tbk engages prim... Outpatient Care; Assistive Technology; Digital... Facilities Support Services, Health Care, Hosp... 2015.000000 2015-10-07 00:00:00 NaN NaT Dec 2025-09-01 Health Care Facilities; Hospitals and Healthca... Health Care Providers and Services Health Care Facilities None Health Care Health Care Equipment and Services Health Care Providers and Services Tangerang None None None NA% None None None None None None None None Pt Anugrah Kasih Rajawali (Current Investment,... 20.370000 6773088070.000000 2438311.705200 NaN NaN NaN 103.000000 None None None NaN NaN None None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
3294 CARE 2011 PT Metro Healthcare Indonesia Tbk (IDX:CARE) 2020-03-11 Health Care Facilities 19890578 IDX Health Care Indonesia Jl. Raya Serang Km 16,8 www.metrohealthcareindonesia.co.id PT Metro Healthcare Indonesia Tbk owns, operat... Public Company None Operating None No None None Yes No No PT Metro Healthcare Indonesia Tbk engages prim... Outpatient Care; Assistive Technology; Digital... Facilities Support Services, Health Care, Hosp... 2015.000000 2015-10-07 00:00:00 NaN NaT Dec 2025-09-01 Health Care Facilities; Hospitals and Healthca... Health Care Providers and Services Health Care Facilities None Health Care Health Care Equipment and Services Health Care Providers and Services Tangerang None None None NA% None None None None None None None None Pt Anugrah Kasih Rajawali (Current Investment,... 20.370000 6773088070.000000 2438311.705200 NaN NaN NaN 103.000000 None None None NaN NaN None None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
3295 CARE 2010 PT Metro Healthcare Indonesia Tbk (IDX:CARE) 2020-03-11 Health Care Facilities 19890578 IDX Health Care Indonesia Jl. Raya Serang Km 16,8 www.metrohealthcareindonesia.co.id PT Metro Healthcare Indonesia Tbk owns, operat... Public Company None Operating None No None None Yes No No PT Metro Healthcare Indonesia Tbk engages prim... Outpatient Care; Assistive Technology; Digital... Facilities Support Services, Health Care, Hosp... 2015.000000 2015-10-07 00:00:00 NaN NaT Dec 2025-09-01 Health Care Facilities; Hospitals and Healthca... Health Care Providers and Services Health Care Facilities None Health Care Health Care Equipment and Services Health Care Providers and Services Tangerang None None None NA% None None None None None None None None Pt Anugrah Kasih Rajawali (Current Investment,... 20.370000 6773088070.000000 2438311.705200 NaN NaN NaN 103.000000 None None None NaN NaN None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
3296 CARE 2009 PT Metro Healthcare Indonesia Tbk (IDX:CARE) 2020-03-11 Health Care Facilities 19890578 IDX Health Care Indonesia Jl. Raya Serang Km 16,8 www.metrohealthcareindonesia.co.id PT Metro Healthcare Indonesia Tbk owns, operat... Public Company None Operating None No None None Yes No No PT Metro Healthcare Indonesia Tbk engages prim... Outpatient Care; Assistive Technology; Digital... Facilities Support Services, Health Care, Hosp... 2015.000000 2015-10-07 00:00:00 NaN NaT Dec 2025-09-01 Health Care Facilities; Hospitals and Healthca... Health Care Providers and Services Health Care Facilities None Health Care Health Care Equipment and Services Health Care Providers and Services Tangerang None None None NA% None None None None None None None None Pt Anugrah Kasih Rajawali (Current Investment,... 20.370000 6773088070.000000 2438311.705200 NaN NaN NaN 103.000000 None None None NaN NaN None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
3297 CARE 2008 PT Metro Healthcare Indonesia Tbk (IDX:CARE) 2020-03-11 Health Care Facilities 19890578 IDX Health Care Indonesia Jl. Raya Serang Km 16,8 www.metrohealthcareindonesia.co.id PT Metro Healthcare Indonesia Tbk owns, operat... Public Company None Operating None No None None Yes No No PT Metro Healthcare Indonesia Tbk engages prim... Outpatient Care; Assistive Technology; Digital... Facilities Support Services, Health Care, Hosp... 2015.000000 2015-10-07 00:00:00 NaN NaT Dec 2025-09-01 Health Care Facilities; Hospitals and Healthca... Health Care Providers and Services Health Care Facilities None Health Care Health Care Equipment and Services Health Care Providers and Services Tangerang None None None NA% None None None None None None None None Pt Anugrah Kasih Rajawali (Current Investment,... 20.370000 6773088070.000000 2438311.705200 NaN NaN NaN 103.000000 None None None NaN NaN None NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

5952 rows × 91 columns

# Drop the identified rows from df_findat_final
df_findat_final = df_findat_final.drop(indices_to_drop)

print("Dataset setelah baris tahun sebelum IPO dihapus :")
display(df_findat_final.head())
print(f"Jumlah baris setelah penghapusan: {len(df_findat_final)}")
Dataset setelah baris tahun sebelum IPO dihapus :
variable ticker Year Entity Name IPO Date MM/dd/yyyy Primary Industry Entity ID Exchange 1st Level Primary Industry Country / Region Name Address 1 Web Address Business Description Company Type Investor Type Company Status Ownership Structure Activist Investor? Yes/No Institution Coverage Level Consolidation Status Has Current Financials? Yes/No Has Bank Regulatory Financials? Yes/No Has Third Party Financials? Yes/No Long Business Description Topic Tags Crunchbase Categories Year Established Date Established MM/dd/yyyy Year Incorporated Date Incorporated MM/dd/yyyy Month of Fiscal Year End Headcount As Of MM/dd/yyyy Industry Classification 2nd Level Primary Industry 3rd Level Primary Industry 4th Level Primary Industry Sector Industry Group Industry City Parent Company Name Parent Ticker Parent Exchange: Ticker Parent Percent Owned (%) Parent City Parent Industry Ultimate Parent Company Name Ultimate Parent Ticker Ultimate Parent Exchange: Ticker Ultimate Parent City Ultimate Parent Industry Ultimate Parent Country / Region All Investors Percent Owned - All Institutions (%) Shares Owned - All Institutions (actual) Market Value - All Institutions (Rp.B) Percent Owned - Insiders (%) Shares Owned - Insiders (actual) Market Value - Insiders (Rp.B) IPO Price (Rp.) Location Type Data Precision Data Year Total Liabilities (Rp.M) Total Assets (Rp.M) Current Ratio (x) Quick Ratio (x) Working Capital (Rp.M) Total Current Assets (Rp.M) Total Current Liabilities (Rp.M) Inventory (Rp.M) Prepaid Exp. (Rp.M) Long-term Debt (Rp.M) Short-term Borrowings (Rp.M) Current Portion of LT Debt & Leases (Rp.M) Total Debt (Rp.M) Total Equity (Rp.M) Net Property, Plant & Equipment (Rp.M) Cost Of Goods Sold (Rp.M) Total Revenue (Rp.M) Operating Income (Rp.M) EBITDA (Rp.M) EBIT (Rp.M) Net Income to Company (Rp.M) Cash from Ops. (Rp.M) Net Change in Cash (Rp.M) ECS Total Common Shares Outstanding (actual) Market Capitalization (Rp.B) Year Close Stock Price (Rp.) Total Capital (Rp.M) Cash & Short-term Investments (Rp.M) Net Intangibles (Rp.M)
17 AALI 2024 PT Astra Agro Lestari Tbk (IDX:AALI) NaT Agricultural Products and Services 4567075 IDX Consumer Indonesia Jl Puloayang Raya www.astra-agro.co.id PT Astra Agro Lestari Tbk engages in the crude... Public Company None Operating Subsidiary None No None None Yes No No PT Astra Agro Lestari Tbk is a prominent playe... None Agriculture, Business Intelligence, Communities 1988.000000 1988-10-03 00:00:00 NaN NaT Dec 2025-09-01 Agricultural Products and Services; Grain And ... Producers Agricultural Products and Services None Consumer Staples Food, Beverage and Tobacco Food Products Jakarta PT Astra International Tbk ASII IDX:ASII 0.796800 Jakarta Automobile Manufacturers Jardine Matheson Holdings Limited J36 SGX:J36 Hamilton Industrial Conglomerates Bermuda PT Astra International Tbk (Current Subsidiary... 2.100000 40473542.000000 315693.627600 NaN NaN NaN NaN None None None 5591163.000000 28793225.000000 2.605000 1.126000 5195985.000000 8433638.000000 3237653.000000 3981669.000000 NaN 1500000.000000 NaN NaN 3189537.000000 23202062.000000 17429693.000000 18358928.000000 21815035.000000 1788141.000000 3202738.000000 1788141.000000 1186783.000000 3379195.000000 1146504.000000 1924688333.000000 11933.067665 6200.000000 26391599.000000 3236012.000000 NaN
18 AALI 2023 PT Astra Agro Lestari Tbk (IDX:AALI) NaT Agricultural Products and Services 4567075 IDX Consumer Indonesia Jl Puloayang Raya www.astra-agro.co.id PT Astra Agro Lestari Tbk engages in the crude... Public Company None Operating Subsidiary None No None None Yes No No PT Astra Agro Lestari Tbk is a prominent playe... None Agriculture, Business Intelligence, Communities 1988.000000 1988-10-03 00:00:00 NaN NaT Dec 2025-09-01 Agricultural Products and Services; Grain And ... Producers Agricultural Products and Services None Consumer Staples Food, Beverage and Tobacco Food Products Jakarta PT Astra International Tbk ASII IDX:ASII 0.796800 Jakarta Automobile Manufacturers Jardine Matheson Holdings Limited J36 SGX:J36 Hamilton Industrial Conglomerates Bermuda PT Astra International Tbk (Current Subsidiary... 2.100000 40473542.000000 315693.627600 NaN NaN NaN NaN None None None 6280237.000000 28846243.000000 1.834000 0.765000 3236061.000000 7118202.000000 3882141.000000 3122454.000000 NaN 1689754.000000 NaN NaN 4005052.000000 22566006.000000 18105649.000000 17950652.000000 20745473.000000 1672092.000000 3007949.000000 1672092.000000 1088170.000000 2538738.000000 469892.000000 1924688333.000000 13520.935539 7025.000000 26571058.000000 2089508.000000 NaN
19 AALI 2022 PT Astra Agro Lestari Tbk (IDX:AALI) NaT Agricultural Products and Services 4567075 IDX Consumer Indonesia Jl Puloayang Raya www.astra-agro.co.id PT Astra Agro Lestari Tbk engages in the crude... Public Company None Operating Subsidiary None No None None Yes No No PT Astra Agro Lestari Tbk is a prominent playe... None Agriculture, Business Intelligence, Communities 1988.000000 1988-10-03 00:00:00 NaN NaT Dec 2025-09-01 Agricultural Products and Services; Grain And ... Producers Agricultural Products and Services None Consumer Staples Food, Beverage and Tobacco Food Products Jakarta PT Astra International Tbk ASII IDX:ASII 0.796800 Jakarta Automobile Manufacturers Jardine Matheson Holdings Limited J36 SGX:J36 Hamilton Industrial Conglomerates Bermuda PT Astra International Tbk (Current Subsidiary... 2.100000 40473542.000000 315693.627600 NaN NaN NaN NaN None None None 7006119.000000 29249340.000000 3.600000 1.227000 5337669.000000 7390608.000000 2052939.000000 3434768.000000 NaN 4048767.000000 NaN NaN 4053767.000000 22243221.000000 17996321.000000 18176356.000000 21828591.000000 2447192.000000 3710458.000000 2447192.000000 1792050.000000 1835397.000000 -2276406.000000 1924688333.000000 15445.623872 8025.000000 26296988.000000 1619616.000000 NaN
20 AALI 2021 PT Astra Agro Lestari Tbk (IDX:AALI) NaT Agricultural Products and Services 4567075 IDX Consumer Indonesia Jl Puloayang Raya www.astra-agro.co.id PT Astra Agro Lestari Tbk engages in the crude... Public Company None Operating Subsidiary None No None None Yes No No PT Astra Agro Lestari Tbk is a prominent playe... None Agriculture, Business Intelligence, Communities 1988.000000 1988-10-03 00:00:00 NaN NaT Dec 2025-09-01 Agricultural Products and Services; Grain And ... Producers Agricultural Products and Services None Consumer Staples Food, Beverage and Tobacco Food Products Jakarta PT Astra International Tbk ASII IDX:ASII 0.796800 Jakarta Automobile Manufacturers Jardine Matheson Holdings Limited J36 SGX:J36 Hamilton Industrial Conglomerates Bermuda PT Astra International Tbk (Current Subsidiary... 2.100000 40473542.000000 315693.627600 NaN NaN NaN NaN None None None 9228733.000000 30399906.000000 1.579000 0.752000 3453812.000000 9414208.000000 5960396.000000 3381506.000000 NaN 2253611.000000 NaN NaN 5818072.000000 21171173.000000 17781227.000000 19442136.000000 24322048.000000 3066334.000000 4314916.000000 3066334.000000 2067362.000000 4895119.000000 2917130.000000 1924688333.000000 18284.539164 9500.000000 26989245.000000 3896022.000000 NaN
21 AALI 2020 PT Astra Agro Lestari Tbk (IDX:AALI) NaT Agricultural Products and Services 4567075 IDX Consumer Indonesia Jl Puloayang Raya www.astra-agro.co.id PT Astra Agro Lestari Tbk engages in the crude... Public Company None Operating Subsidiary None No None None Yes No No PT Astra Agro Lestari Tbk is a prominent playe... None Agriculture, Business Intelligence, Communities 1988.000000 1988-10-03 00:00:00 NaN NaT Dec 2025-09-01 Agricultural Products and Services; Grain And ... Producers Agricultural Products and Services None Consumer Staples Food, Beverage and Tobacco Food Products Jakarta PT Astra International Tbk ASII IDX:ASII 0.796800 Jakarta Automobile Manufacturers Jardine Matheson Holdings Limited J36 SGX:J36 Hamilton Industrial Conglomerates Bermuda PT Astra International Tbk (Current Subsidiary... 2.100000 40473542.000000 315693.627600 NaN NaN NaN NaN None None None 8533437.000000 27781231.000000 3.313000 1.322000 4145384.000000 5937890.000000 1792506.000000 2505048.000000 NaN 5858300.000000 NaN NaN 5858300.000000 19247794.000000 17741481.000000 15809752.000000 18807043.000000 1708412.000000 3031177.000000 1708412.000000 893779.000000 2322164.000000 595526.000000 1924688333.000000 23721.783704 12325.000000 25106094.000000 978892.000000 NaN
Jumlah baris setelah penghapusan: 8549

Sesuaikan Billion ke Million

rp_b_columns = [col for col in df_findat_final.columns if 'Rp.B' in col]

if rp_b_columns:
    for col in rp_b_columns:
        df_findat_final[col] = df_findat_final[col] * 1000
        new_col_name = col.replace('(Rp.B)', '(Rp.M)')
        df_findat_final = df_findat_final.rename(columns={col: new_col_name})
    print(f"Successfully converted {len(rp_b_columns)} column(s) from 'Rp.B' to 'Rp.M' and multiplied values by 1000.")
    print("Updated columns:")
    for col_name in rp_b_columns:
        print(f"- {col_name.replace('(Rp.B)', '(Rp.M)')}")
else:
    print("No columns found with 'Rp.B' in their name in df_findat_final.")
Successfully converted 3 column(s) from 'Rp.B' to 'Rp.M' and multiplied values by 1000.
Updated columns:
- Market Value - All Institutions (Rp.M)
- Market Value - Insiders (Rp.M)
- Market Capitalization (Rp.M)

Hapus Kolom Unused

cols_to_drop = [
    "Location Type",
    "Data Year",
    "Data Precision",
    "Investor Type",
    "Parent Ticker",
    "Ultimate Parent Ticker",
    "Date Incorporated MM/dd/yyyy",
    "Parent Exchange: Ticker",
    "Ownership Structure",
    "4th Level Primary Industry",
    "Year Incorporated",
    "Institution Coverage Level",
    "Ultimate Parent City",
    "Parent City",
    "IPO Price (Rp.)",
    "Ultimate Parent Company Name",
    "Ultimate Parent Country / Region",
    "Parent Company Name",
    "Topic Tags",
    "Long Business Description",
    "Headcount As Of MM/dd/yyyy",
    "Crunchbase Categories",
    "All Investors",
    # "Date Established MM/dd/yyyy",
    "Web Address",
    "Month of Fiscal Year End",
    "Entity ID",
    "Primary Industry",
    "Exchange",
    "Country / Region Name",
    "Address 1",
    "Has Bank Regulatory Financials? Yes/No",
    "Has Current Financials? Yes/No",
    "Has Third Party Financials? Yes/No",
    "Activist Investor? Yes/No",
    "Company Status",
    "Company Type",
    "Business Description",
    "Industry Classification",
    "2nd Level Primary Industry",
    "IPO Date MM/dd/yyyy",
    "3rd Level Primary Industry",
    "City",
    "Industry",
    "Parent Industry",
    "Ultimate Parent Exchange: Ticker",
    "Ultimate Parent Industry",
    "Consolidation Status",
    "Market Value - All Institutions (Rp.M)",
    "Shares Owned - All Institutions (actual)",
    "Market Value - Insiders (Rp.M)",
    "Shares Owned - Insiders (actual)",
]

df_findat_final = df_findat_final.drop(columns=cols_to_drop, errors='ignore')

print(f"Ukuran dataframe setelah drop: {df_findat_final.shape}")
Ukuran dataframe setelah drop: (8549, 40)
print("Sisa kolom:")
df_findat_final.columns.tolist()
Sisa kolom:
['ticker',
 'Year',
 'Entity Name',
 '1st Level Primary Industry',
 'Year Established',
 'Date Established MM/dd/yyyy',
 'Sector',
 'Industry Group',
 'Parent Percent Owned (%)',
 'Percent Owned - All Institutions (%)',
 'Percent Owned - Insiders (%)',
 'Total Liabilities (Rp.M)',
 'Total Assets (Rp.M)',
 'Current Ratio (x)',
 'Quick Ratio (x)',
 'Working Capital (Rp.M)',
 'Total Current Assets (Rp.M)',
 'Total Current Liabilities (Rp.M)',
 'Inventory (Rp.M)',
 'Prepaid Exp. (Rp.M)',
 'Long-term Debt (Rp.M)',
 'Short-term Borrowings (Rp.M)',
 'Current Portion of LT Debt & Leases (Rp.M)',
 'Total Debt (Rp.M)',
 'Total Equity (Rp.M)',
 'Net Property, Plant & Equipment (Rp.M)',
 'Cost Of Goods Sold (Rp.M)',
 'Total Revenue (Rp.M)',
 'Operating Income (Rp.M)',
 'EBITDA (Rp.M)',
 'EBIT (Rp.M)',
 'Net Income to Company (Rp.M)',
 'Cash from Ops. (Rp.M)',
 'Net Change in Cash (Rp.M)',
 'ECS Total Common Shares Outstanding (actual)',
 'Market Capitalization (Rp.M)',
 'Year Close Stock Price (Rp.)',
 'Total Capital (Rp.M)',
 'Cash & Short-term Investments (Rp.M)',
 'Net Intangibles (Rp.M)']

Sesuaikan String NA & NM

Cek kolom non-numerik terlebih dahulu.

numeric_cols = df_findat_final.select_dtypes(include=['number']).columns.tolist()
categorical_cols = df_findat_final.select_dtypes(include=['object']).columns.tolist()

print("Numeric columns:")
print(numeric_cols)
print("\nCategorical columns:")
print(categorical_cols)
Numeric columns:
['Year', 'Year Established', 'Percent Owned - All Institutions (%)', 'Percent Owned - Insiders (%)', 'Total Liabilities (Rp.M)', 'Total Assets (Rp.M)', 'Working Capital (Rp.M)', 'Total Current Assets (Rp.M)', 'Total Current Liabilities (Rp.M)', 'Inventory (Rp.M)', 'Prepaid Exp. (Rp.M)', 'Long-term Debt (Rp.M)', 'Short-term Borrowings (Rp.M)', 'Current Portion of LT Debt & Leases (Rp.M)', 'Total Debt (Rp.M)', 'Total Equity (Rp.M)', 'Net Property, Plant & Equipment (Rp.M)', 'Cost Of Goods Sold (Rp.M)', 'Total Revenue (Rp.M)', 'Operating Income (Rp.M)', 'EBITDA (Rp.M)', 'EBIT (Rp.M)', 'Net Income to Company (Rp.M)', 'Cash from Ops. (Rp.M)', 'Net Change in Cash (Rp.M)', 'ECS Total Common Shares Outstanding (actual)', 'Market Capitalization (Rp.M)', 'Year Close Stock Price (Rp.)', 'Total Capital (Rp.M)', 'Cash & Short-term Investments (Rp.M)', 'Net Intangibles (Rp.M)']

Categorical columns:
['ticker', 'Entity Name', '1st Level Primary Industry', 'Date Established MM/dd/yyyy', 'Sector', 'Industry Group', 'Parent Percent Owned (%)', 'Current Ratio (x)', 'Quick Ratio (x)']

Ubah string NA, NM, atau NAN menjadi null

# Kolom yang akan dicek dan diconvert
target_cols = [
    'ticker',
    'Entity Name',
    '1st Level Primary Industry',
    'Date Established MM/dd/yyyy',
    'Sector',
    'Industry Group',
    'Parent Percent Owned (%)',
    'Current Ratio (x)',
    'Quick Ratio (x)'
]

# Pastikan hanya menggunakan kolom yang ada di dataframe (untuk menghindari error jika ada typo nama kolom)
# valid_cols = [c for c in target_cols if c in df_findat_final.columns]
valid_cols = target_cols

# Pola Regex
# Menangkap: "na", "nan", "nm" dengan variasi spasi di awal
# dan variasi spasi/persen di akhir.
trash_pattern_specific = r'(?i)^\s*(?:NA|NAN|NM)[\s%]*$'

# --- TAHAP 1: DETEKSI & TAMPILKAN ---

# Buat filter hanya pada kolom target
# Convert ke string sementara (.astype(str)) agar regex berjalan lancar
mask = df_findat_final[valid_cols].astype(str).apply(
    lambda x: x.str.contains(trash_pattern_specific, regex=True)
)

# Ambil baris yang setidaknya memiliki 1 nilai sampah di kolom target
rows_with_trash = df_findat_final[mask.any(axis=1)]

print(f"Ditemukan {len(rows_with_trash)} baris yang mengandung 'NA', 'NAN', atau 'NM' pada kolom target.")

if len(rows_with_trash) > 0:
    print("\n--- Preview Data (Hanya Kolom Target) ---")
    # Menampilkan hanya kolom target sesuai permintaan
    display(rows_with_trash[valid_cols])

# --- TAHAP 2: UBAH JADI NULL ---

# Lakukan replace hanya pada kolom target
df_findat_final[valid_cols] = df_findat_final[valid_cols].replace(
    to_replace=trash_pattern_specific,
    value=np.nan,
    regex=True
)

print("\n[SUKSES] Nilai tersebut telah diubah menjadi Null (NaN).")
Ditemukan 3806 baris yang mengandung 'NA', 'NAN', atau 'NM' pada kolom target.

--- Preview Data (Hanya Kolom Target) ---
variable ticker Entity Name 1st Level Primary Industry Date Established MM/dd/yyyy Sector Industry Group Parent Percent Owned (%) Current Ratio (x) Quick Ratio (x)
136 ADCP PT Adhi Commuter Properti Tbk (IDX:ADCP) Real Estate 2018-03-09 00:00:00 Real Estate Real Estate Management and Development NA% 1.441000 0.047000
137 ADCP PT Adhi Commuter Properti Tbk (IDX:ADCP) Real Estate 2018-03-09 00:00:00 Real Estate Real Estate Management and Development NA% 1.333000 0.133000
138 ADCP PT Adhi Commuter Properti Tbk (IDX:ADCP) Real Estate 2018-03-09 00:00:00 Real Estate Real Estate Management and Development NA% 0.960000 0.064000
170 ADHI PT Adhi Karya (Persero) Tbk (IDX:ADHI) Industrials 1960-03-11 00:00:00 Industrials Capital Goods NA% 1.123000 0.697000
171 ADHI PT Adhi Karya (Persero) Tbk (IDX:ADHI) Industrials 1960-03-11 00:00:00 Industrials Capital Goods NA% 1.144000 0.843000
... ... ... ... ... ... ... ... ... ...
15678 ZONE PT Mega Perintis Tbk (IDX:ZONE) Consumer 2005-10-21 00:00:00 Consumer Discretionary Consumer Discretionary Distribution and Retail NA% 1.583000 0.168000
15679 ZONE PT Mega Perintis Tbk (IDX:ZONE) Consumer 2005-10-21 00:00:00 Consumer Discretionary Consumer Discretionary Distribution and Retail NA% 2.369000 0.477000
15691 ZYRX PT Zyrexindo Mandiri Buana Tbk (IDX:ZYRX) Technology, Media & Telecommunications None Information Technology Technology Hardware and Equipment NA% 3.402000 0.775000
15692 ZYRX PT Zyrexindo Mandiri Buana Tbk (IDX:ZYRX) Technology, Media & Telecommunications None Information Technology Technology Hardware and Equipment NA% 2.073000 0.401000
15693 ZYRX PT Zyrexindo Mandiri Buana Tbk (IDX:ZYRX) Technology, Media & Telecommunications None Information Technology Technology Hardware and Equipment NA% 1.442000 0.668000

3806 rows × 9 columns


[SUKSES] Nilai tersebut telah diubah menjadi Null (NaN).
/tmp/ipykernel_108742/3875649896.py:44: FutureWarning:

Downcasting behavior in `replace` is deprecated and will be removed in a future version. To retain the old behavior, explicitly call `result.infer_objects(copy=False)`. To opt-in to the future behavior, set `pd.set_option('future.no_silent_downcasting', True)`
print("--- HASIL VERIFIKASI KEBERSIHAN DATA ---")
print(f"Mengecek {len(valid_cols)} kolom target...\n")

total_sisa = 0

for col in valid_cols:
    # LANGKAH KUNCI:
    # .dropna() membuang Null asli agar tidak diubah jadi string "nan"
    # .astype(str) mengubah sisa data menjadi string untuk dicek regex
    sisa_data = df_findat_final[col].dropna().astype(str)

    # Hitung berapa yang cocok dengan pola sampah
    jumlah_sampah = sisa_data.str.contains(trash_pattern_specific, regex=True).sum()

    if jumlah_sampah > 0:
        print(f"[!] Kolom '{col}' MASIH KOTOR: ditemukan {jumlah_sampah} nilai.")
        total_sisa += jumlah_sampah
    else:
        print(f"[OK] Kolom '{col}': Bersih.")

print("-" * 40)
if total_sisa == 0:
    print("KESIMPULAN: Sukses! Tidak ada lagi nilai NA, NAN, atau NM di kolom-kolom tersebut.")
else:
    print(f"PERINGATAN: Masih ada total {total_sisa} nilai sampah yang tertinggal.")
--- HASIL VERIFIKASI KEBERSIHAN DATA ---
Mengecek 9 kolom target...

[OK] Kolom 'ticker': Bersih.
[OK] Kolom 'Entity Name': Bersih.
[OK] Kolom '1st Level Primary Industry': Bersih.
[OK] Kolom 'Date Established MM/dd/yyyy': Bersih.
[OK] Kolom 'Sector': Bersih.
[OK] Kolom 'Industry Group': Bersih.
[OK] Kolom 'Parent Percent Owned (%)': Bersih.
[OK] Kolom 'Current Ratio (x)': Bersih.
[OK] Kolom 'Quick Ratio (x)': Bersih.
----------------------------------------
KESIMPULAN: Sukses! Tidak ada lagi nilai NA, NAN, atau NM di kolom-kolom tersebut.

Ubah Kolom Numerik yang Masih String

numeric_cols = df_findat_final.select_dtypes(include=['number']).columns.tolist()
categorical_cols = df_findat_final.select_dtypes(include=['object']).columns.tolist()

print("Numeric columns:")
print(numeric_cols)
print("\nCategorical columns:")
print(categorical_cols)
Numeric columns:
['Year', 'Year Established', 'Percent Owned - All Institutions (%)', 'Percent Owned - Insiders (%)', 'Total Liabilities (Rp.M)', 'Total Assets (Rp.M)', 'Current Ratio (x)', 'Quick Ratio (x)', 'Working Capital (Rp.M)', 'Total Current Assets (Rp.M)', 'Total Current Liabilities (Rp.M)', 'Inventory (Rp.M)', 'Prepaid Exp. (Rp.M)', 'Long-term Debt (Rp.M)', 'Short-term Borrowings (Rp.M)', 'Current Portion of LT Debt & Leases (Rp.M)', 'Total Debt (Rp.M)', 'Total Equity (Rp.M)', 'Net Property, Plant & Equipment (Rp.M)', 'Cost Of Goods Sold (Rp.M)', 'Total Revenue (Rp.M)', 'Operating Income (Rp.M)', 'EBITDA (Rp.M)', 'EBIT (Rp.M)', 'Net Income to Company (Rp.M)', 'Cash from Ops. (Rp.M)', 'Net Change in Cash (Rp.M)', 'ECS Total Common Shares Outstanding (actual)', 'Market Capitalization (Rp.M)', 'Year Close Stock Price (Rp.)', 'Total Capital (Rp.M)', 'Cash & Short-term Investments (Rp.M)', 'Net Intangibles (Rp.M)']

Categorical columns:
['ticker', 'Entity Name', '1st Level Primary Industry', 'Date Established MM/dd/yyyy', 'Sector', 'Industry Group', 'Parent Percent Owned (%)']

Parent Percent Owned (%)

df_findat_final['Parent Percent Owned (%)'].unique()
array([0.7968, 0.5116, 0.8776, 0.5355, 0.6, 0.501, nan, 0.9152, 0.9207,
       0.495, 0.84, 0.4566, 0.8685, 0.9149, 0.6257, 0.8363, 0.568, 0.7421,
       0.596, 0.9825, 0.6875, 0.5109, 0.8079, 0.9604, 0.7095, 0.5839,
       0.9977, 0.5319, 0.9592, 0.65, 0.5784, 0.588, 0.8272, 1, 0.7333,
       0.7686, 0.5011, 0.7739, 0.776, 0.0101, 0.5146, 0.8, 0.9999, 0.6969,
       0.9235, 0.747, 0.5969, 0.5367, 0.8201, 0.5494, 0.6088, 0.6688,
       0.676, 0.8944, 0.5682, 0.75, 0.7291, 0.3445, 0.5048, 0.7317,
       0.7059, 0.6611, 0.7911, 0.7929, 0.4815, 0.2393, 0.54,
       '15.85%; 6.30%', 0.8656, 0.2283, 0.5249, 0.3852, 0.5113, 0.5267,
       0.9157, 0.51, 0.811, 0.5737, 0.52, 0.4575, 0.2953, 0.9144,
       '33.95%; 45.02%', 0.9871, 0.576, 0.8451, 0.6577, 0.6159, 0.6467,
       0.5147, 0.2009, 0.5456, 0.6714, 0.5999, 0.5, 0.9096, 0.9105, 0.7,
       0.4423, 0.5502, 0.6706, 0.6113, 0.68, 0.8702, 0.9616, 0.5149,
       0.5961, 0.7021, 0.606, 0.7777, 0.5577, 0.5099, 0.5475, 0.0963,
       0.6112, 0.5553, 0.32, 0.5723, 0.5284, 0.7386, 0.5343, 0.5229,
       0.6163, 0.5833, 0.5728, 0.8973, 0.9961, 0.5118, 0.6929, 0.5989,
       0.8856, 0.9246, 0.4852, 0.8213, 0.8236, 0.511, 0.6677, 0.9247,
       0.5469, 0.7999, 0.5646, 0.6817, 0.8689, 0.7926, 0.997, 0.7906,
       '50.00%; 50.00%', 0.7733, 0.925, 0.6107, 0.7981, 0.85, 0.7474,
       0.6928, 0.5043, 0.72, 0.7517, 0.6828, 0.848, 0.708, 0.996, 0.5434,
       0.6565, 0.6358, 0.4943, 0.7832, 0.7084, 0.7979, 0.7199, 0.5507,
       0.7017, 0.9998, 0.5566, 0.7942, 0.9146, 0.9764, 0.4949, 0.9197,
       0.8066, 0.5007, 0.6728, 0.881, 0.5628, 0.5554, 0.0538, 0.7371,
       0.9242, 0.5473, 0.5604, 0.7128, 0.6067, 0.7848, 0.99, 0.6564,
       0.569, 0.6514, 0.9991, 0.5256, 0.4003, 0.5795, 0.5298, 0.6402,
       0.5767, 0.9002, 0.8019, 0.5421, 0.4947, 0.5602, 0.7481, 0.6607,
       0.8031, 0.9204, 0.7692, 0.525, 0.9987, 0.55, 0.625, 0.8842, 0.9909,
       0.727, 0.5075, 0.9, 0.6252, 0.8083, 0.8171, 0.57, 0.6752, 0.5714,
       0.595, 0.5572, 0.5727, 0.6884, 0.7909, 0.4239, 0.5004, 0.6726,
       0.515, 0.5802, 0.7399, 0.763, 0.9935, 0.7874, 0.5649, 0.7709,
       0.6187, 0.8178, 0.4203, 0.8694, 0.8367, 0.5138, 0.5669, 0.505,
       0.7126, 0.2053, 0.7257, 0.919, 0.9233, 0.8679, 0.5903, 0.7505,
       0.5951, 0.8469, 0.8489, 0.805, 0.5128, 0.6344, 0.4, 0.7525, 0.8508,
       0.2397, 0.6098, 0.833, 0.6215, 0.7466, 0.6293, 0.5677, 0.5696,
       0.6901, 0.6196, 0.5009, 0.7118, 0.4604, 0.673, 0.2971, 0.6786,
       0.557, 0.8498, 0.5785, 0.6495, 0.6593, 0.8182, 0.97, 0.9912,
       0.6868, 0.685, 0.6479, 0.5886, 0.7056, 0.7792, 0.8649, 0.8054,
       0.8492, 0.993, 0.7984, 0.777, 0.7188, 0.7426, 0.6301, 0.7136, 0.45,
       0.9075, 0.668, 0.9194, 0.6572, 0.7897, 0.8162, 0.7346, 0.5975,
       0.7125, 0.9239, 0.7551, 0.8352, 0.9897, 0.5797, 0.512, 0.8007,
       0.5611, 0.5104, 0.7165, 0.3383, 0.5812, 0.7989, 0.793, 0.7562,
       0.5027, 0.5375, 0.7665, 0.5676, 0.9996, 0.7722, 0.678, 0.9189,
       0.629, 0.7115, 0.7995, 0.1646, 0.6934, 0.8465, 0.7354, 0.5967,
       0.5209, 0.8522, 0.6191, 0.761, 0.5875, 0.565, 0.5246, 0.5956,
       0.5112, 0.6218, 0.8381, 0.266, 0.5283, 0.6415, 0.7753, 0.585,
       0.6055, 0.5929, 0.2378, 0.7429, 0.6998, 0.7295, 0.5694, 0.8966,
       0.6744, 0.701, 0.6505, 0.6749, 0.6815, 0.7534, 0.8946, 0.912,
       0.6321], dtype=object)

Terlihat bahwa sebagian besar data pada kolom ‘Parent Percent Owned (%)’ masih berupa desimal, belum persentase. Ada juga yang berisi 2 data karena memiliki 2 parent. Untuk itu data kolom ini perlu disesuaikan.

def clean_ownership_data(value):
    # handle "NA%" atau data kosong/null
    if pd.isna(value) or value == "NA%":
        return np.nan

    val_str = str(value).strip() # trim spasi

    # handle jika ada MULTIPLE values (dipisahkan titik koma ';')
    # Contoh: "15.85; 6.30" atau "50%; 25%"
    if ";" in val_str:
        parts = val_str.split(";")
        total = 0
        valid_parts = 0
        for part in parts:
            # Panggil fungsi ini sendiri secara rekursif untuk setiap bagian
            cleaned_part = clean_ownership_data(part)
            if pd.notna(cleaned_part):
                total += cleaned_part
                valid_parts += 1
        return total if valid_parts > 0 else np.nan

    # handle format persentase (misal 50.25%) -> hapus % jadi float
    if "%" in val_str:
        try:
            return float(val_str.replace("%", ""))
        except ValueError:
            return np.nan

    # 4. Handle format desimal atau angka biasa
    else:
        try:
            float_val = float(val_str)

            # LOGIC TAMBAHAN :
            # angka <= 1, kemungkinan desimal (0.50 -> 50%)
            # angka > 1, kemungkinan sudah persen tapi tanpa simbol (15.85 -> 15.85%)
            if float_val <= 1.0:
                return float_val * 100
            else:
                return float_val
        except ValueError:
            return np.nan

# terapkan
df_findat_final["Parent Percent Owned (%)"] = df_findat_final["Parent Percent Owned (%)"].apply(clean_ownership_data)

# cek hasil
print("Statistik Data:")
print(df_findat_final["Parent Percent Owned (%)"].describe())
print("\nContoh data:")
print(df_findat_final[["ticker", "Year", "Parent Percent Owned (%)"]].sample(10))
Statistik Data:
count   4868.000000
mean      66.388841
std       17.783044
min        1.010000
25%       54.892500
50%       65.140000
75%       80.000000
max      100.000000
Name: Parent Percent Owned (%), dtype: float64

Contoh data:
variable ticker  Year  Parent Percent Owned (%)
5571       GEMS  2012                 51.000000
1859       BCIP  2018                       NaN
4440       DMAS  2021                 57.280000
2258       BISI  2010                       NaN
1221       ASRM  2010                       NaN
12480      RUNS  2022                       NaN
11427      POLA  2021                 99.990000
6741       IMPC  2015                       NaN
15423      WTON  2020                 60.000000
5557       GEMA  2009                 74.740000

Tambah Kolom Tahun IPO

dict_perusahaan_tahun_ipo = dict(zip(df_emiten_idx['Kode'], df_emiten_idx['Tanggal Pencatatan'].dt.year))
print(dict_perusahaan_tahun_ipo)
{'AALI': 1997, 'ABBA': 2002, 'ABDA': 1989, 'ABMM': 2011, 'ACES': 2007, 'ACST': 2013, 'ADES': 1994, 'ADHI': 2004, 'ADMF': 2004, 'ADMG': 1993, 'ADRO': 2008, 'AGII': 2016, 'AGRO': 2003, 'AGRS': 2014, 'AHAP': 1990, 'AIMS': 2001, 'AISA': 1997, 'AKKU': 2004, 'AKPI': 1992, 'AKRA': 1994, 'AKSI': 2001, 'ALDO': 2011, 'ALKA': 1990, 'ALMI': 1997, 'ALTO': 2012, 'AMAG': 2005, 'AMFG': 1995, 'AMIN': 2015, 'AMRT': 2009, 'ANJT': 2013, 'ANTM': 1997, 'APEX': 2013, 'APIC': 2002, 'APII': 2013, 'APLI': 2000, 'APLN': 2010, 'ARGO': 1991, 'ARII': 2011, 'ARNA': 2001, 'ARTA': 2002, 'ARTI': 2003, 'ARTO': 2016, 'ASBI': 1989, 'ASDM': 1989, 'ASGR': 1989, 'ASII': 1990, 'ASJT': 2003, 'ASMI': 2014, 'ASRI': 2007, 'ASRM': 1990, 'ASSA': 2012, 'ATIC': 2015, 'AUTO': 1998, 'BABP': 2002, 'BACA': 2007, 'BAJA': 2011, 'BALI': 2014, 'BAPA': 2008, 'BATA': 1982, 'BAYU': 1989, 'BBCA': 2000, 'BBHI': 2015, 'BBKP': 2006, 'BBLD': 1990, 'BBMD': 2013, 'BBNI': 1996, 'BBRI': 2003, 'BBRM': 2013, 'BBTN': 2009, 'BBYB': 2015, 'BCAP': 2001, 'BCIC': 1997, 'BCIP': 2009, 'BDMN': 1989, 'BEKS': 2001, 'BEST': 2012, 'BFIN': 1990, 'BGTG': 2016, 'BHIT': 1997, 'BIKA': 2015, 'BIMA': 1994, 'BINA': 2014, 'BIPI': 2010, 'BIPP': 1995, 'BIRD': 2014, 'BISI': 2007, 'BJBR': 2010, 'BJTM': 2012, 'BKDP': 2007, 'BKSL': 1997, 'BKSW': 2002, 'BLTA': 1990, 'BLTZ': 2014, 'BMAS': 2013, 'BMRI': 2003, 'BMSR': 1999, 'BMTR': 1995, 'BNBA': 2006, 'BNBR': 1989, 'BNGA': 1989, 'BNII': 1989, 'BNLI': 1990, 'BOLT': 2015, 'BPFI': 2009, 'BPII': 2014, 'BRAM': 1990, 'BRMS': 2010, 'BRNA': 1989, 'BRPT': 1993, 'BSDE': 2008, 'BSIM': 2010, 'BSSR': 2012, 'BSWD': 2002, 'BTEK': 2004, 'BTEL': 2006, 'BTON': 2001, 'BTPN': 2008, 'BUDI': 1995, 'BUKK': 2015, 'BULL': 2011, 'BUMI': 1990, 'BUVA': 2010, 'BVIC': 1999, 'BWPT': 2009, 'BYAN': 2008, 'CANI': 2014, 'CASS': 2011, 'CEKA': 1996, 'CENT': 2001, 'CFIN': 1990, 'CINT': 2014, 'CITA': 2002, 'CLPI': 2001, 'CMNP': 1995, 'CMPP': 1994, 'CNKO': 2001, 'CNTX': 1979, 'COWL': 2007, 'CPIN': 1991, 'CPRO': 2006, 'CSAP': 2007, 'CTBN': 1989, 'CTRA': 1994, 'CTTH': 1996, 'DART': 1990, 'DEFI': 2001, 'DEWA': 2007, 'DGIK': 2007, 'DILD': 1991, 'DKFT': 1997, 'DLTA': 1984, 'DMAS': 2015, 'DNAR': 2014, 'DNET': 2000, 'DOID': 2001, 'DPNS': 1990, 'DSFI': 2000, 'DSNG': 2013, 'DSSA': 2009, 'DUTI': 1994, 'DVLA': 1994, 'DYAN': 2013, 'ECII': 2013, 'EKAD': 1990, 'ELSA': 2008, 'ELTY': 1995, 'EMDE': 2011, 'EMTK': 2010, 'ENRG': 2004, 'EPMT': 1994, 'ERAA': 2011, 'ERTX': 1990, 'ESSA': 2012, 'ESTI': 1992, 'ETWA': 1997, 'EXCL': 2005, 'FAST': 1993, 'FASW': 1994, 'FISH': 2002, 'FMII': 2000, 'FORU': 2002, 'FPNI': 2002, 'GAMA': 2012, 'GDST': 2009, 'GDYR': 1980, 'GEMA': 2002, 'GEMS': 2011, 'GGRM': 1990, 'GIAA': 2011, 'GJTL': 1990, 'GLOB': 2012, 'GMTD': 2000, 'GOLD': 2010, 'GOLL': 2014, 'GPRA': 2007, 'GSMF': 1989, 'GTBO': 2009, 'GWSA': 2011, 'GZCO': 2008, 'HADE': 2004, 'HDFA': 2011, 'HERO': 1989, 'HEXA': 1995, 'HITS': 1997, 'HMSP': 1990, 'HOME': 2008, 'HOTL': 2013, 'HRUM': 2010, 'IATA': 2006, 'IBFN': 2014, 'IBST': 2012, 'ICBP': 2010, 'ICON': 2005, 'IGAR': 1990, 'IIKP': 2002, 'IKAI': 1997, 'IKBI': 1991, 'IMAS': 1993, 'IMJS': 2013, 'IMPC': 2014, 'INAF': 2001, 'INAI': 1994, 'INCI': 1990, 'INCO': 1990, 'INDF': 1994, 'INDR': 1990, 'INDS': 1990, 'INDX': 2001, 'INDY': 2008, 'INKP': 1990, 'INPC': 1990, 'INPP': 2004, 'INRU': 1990, 'INTA': 1993, 'INTD': 1989, 'INTP': 1989, 'IPOL': 2010, 'ISAT': 1994, 'ISSP': 2013, 'ITMA': 1990, 'ITMG': 2007, 'JAWA': 2011, 'JECC': 1992, 'JIHD': 1984, 'JKON': 2007, 'JPFA': 1989, 'JRPT': 1994, 'JSMR': 2007, 'JSPT': 1998, 'JTPE': 2002, 'KAEF': 2001, 'KARW': 1994, 'KBLI': 1992, 'KBLM': 1992, 'KBLV': 2000, 'KBRI': 2008, 'KDSI': 1996, 'KIAS': 1994, 'KICI': 1993, 'KIJA': 1995, 'KKGI': 1991, 'KLBF': 1991, 'KOBX': 2012, 'KOIN': 2008, 'KONI': 1995, 'KOPI': 2015, 'KPIG': 2000, 'KRAS': 2010, 'KREN': 2002, 'LAPD': 2001, 'LCGP': 2007, 'LEAD': 2013, 'LINK': 2014, 'LION': 1993, 'LMAS': 2001, 'LMPI': 1994, 'LMSH': 1990, 'LPCK': 1997, 'LPGI': 1997, 'LPIN': 1990, 'LPKR': 1996, 'LPLI': 1989, 'LPPF': 1989, 'LPPS': 1994, 'LRNA': 2014, 'LSIP': 1996, 'LTLS': 1997, 'MAGP': 2013, 'MAIN': 2006, 'MAPI': 2004, 'MASA': 2005, 'MAYA': 1997, 'MBAP': 2014, 'MBSS': 2011, 'MBTO': 2011, 'MCOR': 2007, 'MDIA': 2014, 'MDKA': 2015, 'MDLN': 1993, 'MDRN': 1991, 'MEDC': 1994, 'MEGA': 2000, 'MERK': 1981, 'META': 2001, 'MFIN': 2005, 'MFMI': 2010, 'MGNA': 2014, 'MICE': 2005, 'MIDI': 2010, 'MIKA': 2015, 'MIRA': 1997, 'MITI': 1997, 'MKPI': 2009, 'MLBI': 1981, 'MLIA': 1994, 'MLPL': 1989, 'MLPT': 2013, 'MMLP': 2015, 'MNCN': 2007, 'MPMX': 2013, 'MPPA': 1992, 'MRAT': 1995, 'MREI': 1989, 'MSKY': 2012, 'MTDL': 1990, 'MTFN': 1990, 'MTLA': 2011, 'MTSM': 1992, 'MYOH': 2000, 'MYOR': 1990, 'MYTX': 1989, 'NELY': 2012, 'NIKL': 2009, 'NIRO': 2012, 'NISP': 1994, 'NOBU': 2013, 'NRCA': 2013, 'OCAP': 2003, 'OKAS': 2006, 'OMRE': 1994, 'PADI': 2012, 'PALM': 2012, 'PANR': 2001, 'PANS': 2000, 'PBRX': 1990, 'PDES': 2008, 'PEGE': 2005, 'PGAS': 2003, 'PGLI': 2000, 'PICO': 1996, 'PJAA': 2004, 'PKPK': 2007, 'PLAS': 2001, 'PLIN': 1992, 'PNBN': 1982, 'PNBS': 2014, 'PNIN': 1983, 'PNLF': 1983, 'PNSE': 1990, 'POLY': 1991, 'POOL': 1991, 'PPRO': 2015, 'PSAB': 2003, 'PSDN': 1994, 'PSKT': 1995, 'PTBA': 2002, 'PTIS': 2011, 'PTPP': 2010, 'PTRO': 1990, 'PTSN': 2007, 'PTSP': 1994, 'PUDP': 1994, 'PWON': 1989, 'PYFA': 2001, 'RAJA': 2006, 'RALS': 1996, 'RANC': 2012, 'RBMS': 1997, 'RDTX': 1990, 'RELI': 2005, 'RICY': 1998, 'RIGS': 1990, 'RIMO': 2000, 'RODA': 2001, 'ROTI': 2010, 'RUIS': 2006, 'SAFE': 1994, 'SAME': 2013, 'SCCO': 1982, 'SCMA': 2002, 'SCPI': 1990, 'SDMU': 2011, 'SDPC': 1990, 'SDRA': 2006, 'SGRO': 2007, 'SHID': 1990, 'SIDO': 2013, 'SILO': 2013, 'SIMA': 1994, 'SIMP': 2011, 'SIPD': 1996, 'SKBM': 2012, 'SKLT': 1993, 'SKYB': 2010, 'SMAR': 1992, 'SMBR': 2013, 'SMCB': 1977, 'SMDM': 1995, 'SMDR': 1999, 'SMGR': 1991, 'SMMA': 1995, 'SMMT': 1997, 'SMRA': 1990, 'SMRU': 2011, 'SMSM': 1996, 'SOCI': 2014, 'SONA': 1992, 'SPMA': 1994, 'SQMI': 2004, 'SRAJ': 2011, 'SRIL': 2013, 'SRSN': 1993, 'SRTG': 2013, 'SSIA': 1997, 'SSMS': 2013, 'SSTM': 1997, 'STAR': 2011, 'STTP': 1996, 'SUGI': 2002, 'SULI': 1994, 'SUPR': 2011, 'TALF': 2014, 'TARA': 2014, 'TAXI': 2012, 'TBIG': 2010, 'TBLA': 2000, 'TBMS': 1990, 'TCID': 1993, 'TELE': 2012, 'TFCO': 1980, 'TGKA': 1990, 'TIFA': 2011, 'TINS': 1995, 'TIRA': 1993, 'TIRT': 1999, 'TKIM': 1990, 'TLKM': 1995, 'TMAS': 2003, 'TMPO': 2001, 'TOBA': 2012, 'TOTL': 2006, 'TOTO': 1990, 'TOWR': 2010, 'TPIA': 2008, 'TPMA': 2013, 'TRAM': 2008, 'TRIL': 2008, 'TRIM': 2000, 'TRIO': 2009, 'TRIS': 2012, 'TRST': 1990, 'TRUS': 2002, 'TSPC': 1994, 'ULTJ': 1990, 'UNIC': 1989, 'UNIT': 2002, 'UNSP': 1990, 'UNTR': 1989, 'UNVR': 1982, 'VICO': 2013, 'VINS': 2015, 'VIVA': 2011, 'VOKS': 1990, 'VRNA': 2008, 'WAPO': 2001, 'WEHA': 2007, 'WICO': 1994, 'WIIM': 2012, 'WIKA': 2007, 'WINS': 2010, 'WOMF': 2004, 'WSKT': 2012, 'WTON': 2014, 'YPAS': 2008, 'YULE': 2004, 'ZBRA': 1991, 'SHIP': 2016, 'CASA': 2016, 'DAYA': 2016, 'DPUM': 2015, 'IDPR': 2015, 'JGLE': 2016, 'KINO': 2015, 'MARI': 2016, 'MKNT': 2015, 'MTRA': 2016, 'OASA': 2016, 'POWR': 2016, 'INCF': 2016, 'WSBP': 2016, 'PBSA': 2016, 'PRDA': 2016, 'BOGA': 2016, 'BRIS': 2018, 'PORT': 2017, 'CARS': 2017, 'MINA': 2017, 'CLEO': 2017, 'TAMU': 2017, 'CSIS': 2017, 'TGRA': 2017, 'FIRE': 2017, 'TOPS': 2017, 'KMTR': 2017, 'ARMY': 2017, 'MAPB': 2017, 'WOOD': 2017, 'HRTA': 2017, 'MABA': 2017, 'HOKI': 2017, 'MPOW': 2017, 'MARK': 2017, 'NASA': 2017, 'MDKI': 2017, 'BELL': 2017, 'KIOS': 2017, 'GMFI': 2017, 'MTWI': 2017, 'ZINC': 2017, 'MCAS': 2017, 'PPRE': 2017, 'WEGE': 2017, 'PSSI': 2017, 'MORA': 2022, 'DWGL': 2017, 'PBID': 2017, 'JMAS': 2017, 'CAMP': 2017, 'IPCM': 2017, 'PCAR': 2017, 'LCKM': 2018, 'BOSS': 2018, 'HELI': 2018, 'JSKY': 2018, 'INPS': 2018, 'GHON': 2018, 'TDPM': 2018, 'DFAM': 2018, 'NICK': 2018, 'BTPS': 2018, 'SPTO': 2018, 'PRIM': 2018, 'HEAL': 2018, 'TRUK': 2018, 'PZZA': 2018, 'TUGU': 2018, 'MSIN': 2018, 'SWAT': 2018, 'TNCA': 2018, 'MAPA': 2018, 'TCPI': 2018, 'IPCC': 2018, 'RISE': 2018, 'BPTR': 2018, 'POLL': 2018, 'NFCX': 2018, 'MGRO': 2018, 'NUSA': 2018, 'FILM': 2018, 'ANDI': 2018, 'LAND': 2018, 'MOLI': 2018, 'PANI': 2018, 'DIGI': 2018, 'CITY': 2018, 'SAPX': 2018, 'SURE': 2018, 'HKMU': 2018, 'MPRO': 2018, 'DUCK': 2018, 'GOOD': 2018, 'SKRN': 2018, 'YELO': 2018, 'CAKK': 2018, 'SATU': 2018, 'SOSS': 2018, 'DEAL': 2018, 'POLA': 2018, 'DIVA': 2018, 'LUCK': 2018, 'URBN': 2018, 'SOTS': 2018, 'ZONE': 2018, 'PEHA': 2018, 'FOOD': 2019, 'BEEF': 2019, 'POLI': 2019, 'CLAY': 2019, 'NATO': 2019, 'JAYA': 2019, 'COCO': 2019, 'MTPS': 2019, 'CPRI': 2019, 'HRME': 2019, 'POSA': 2019, 'JAST': 2019, 'FITT': 2019, 'BOLA': 2019, 'CCSI': 2019, 'SFAN': 2019, 'POLU': 2019, 'KJEN': 2019, 'KAYU': 2019, 'ITIC': 2019, 'PAMG': 2019, 'IPTV': 2019, 'BLUE': 2019, 'ENVY': 2019, 'EAST': 2019, 'LIFE': 2019, 'FUJI': 2019, 'KOTA': 2019, 'INOV': 2019, 'ARKA': 2019, 'SMKL': 2019, 'HDIT': 2019, 'KEEN': 2019, 'BAPI': 2019, 'TFAS': 2019, 'GGRP': 2019, 'OPMS': 2019, 'NZIA': 2019, 'SLIS': 2019, 'PURE': 2019, 'IRRA': 2019, 'DMMX': 2019, 'SINI': 2019, 'WOWS': 2019, 'ESIP': 2019, 'TEBE': 2019, 'KEJU': 2019, 'PSGO': 2019, 'AGAR': 2019, 'IFSH': 2019, 'REAL': 2019, 'IFII': 2019, 'PMJS': 2019, 'UCID': 2019, 'GLVA': 2019, 'PGJO': 2020, 'AMAR': 2020, 'CSRA': 2020, 'INDO': 2020, 'AMOR': 2020, 'TRIN': 2020, 'DMND': 2020, 'PURA': 2020, 'PTPW': 2020, 'TAMA': 2020, 'IKAN': 2020, 'SAMF': 2020, 'SBAT': 2020, 'KBAG': 2020, 'CBMF': 2020, 'RONY': 2020, 'CSMI': 2020, 'BBSS': 2020, 'BHAT': 2020, 'CASH': 2020, 'TECH': 2020, 'EPAC': 2020, 'UANG': 2020, 'PGUN': 2020, 'SOFA': 2020, 'PPGL': 2020, 'TOYS': 2020, 'SGER': 2020, 'TRJA': 2020, 'PNGO': 2020, 'SCNP': 2020, 'BBSI': 2020, 'KMDS': 2020, 'PURI': 2020, 'SOHO': 2020, 'HOMI': 2020, 'ROCK': 2020, 'ENZO': 2020, 'PLAN': 2020, 'PTDU': 2020, 'ATAP': 2020, 'VICI': 2020, 'PMMP': 2020, 'WIFI': 2020, 'FAPA': 2021, 'DCII': 2021, 'KETR': 2021, 'DGNS': 2021, 'UFOE': 2021, 'BANK': 2021, 'WMUU': 2021, 'EDGE': 2021, 'UNIQ': 2021, 'BEBS': 2021, 'SNLK': 2021, 'ZYRX': 2021, 'LFLO': 2021, 'FIMP': 2021, 'TAPG': 2021, 'NPGF': 2021, 'LUCY': 2021, 'ADCP': 2021, 'HOPE': 2021, 'MGLV': 2021, 'TRUE': 2021, 'LABA': 2021, 'ARCI': 2021, 'IPAC': 2021, 'MASB': 2021, 'BMHS': 2021, 'FLMC': 2021, 'NICL': 2021, 'UVCR': 2021, 'BUKA': 2021, 'HAIS': 2021, 'OILS': 2021, 'GPSO': 2021, 'MCOL': 2021, 'RSGK': 2021, 'RUNS': 2021, 'SBMA': 2021, 'CMNT': 2021, 'GTSI': 2021, 'IDEA': 2021, 'KUAS': 2021, 'BOBA': 2021, 'MTEL': 2021, 'DEPO': 2021, 'BINO': 2021, 'CMRY': 2021, 'WGSH': 2021, 'TAYS': 2021, 'WMPP': 2021, 'RMKE': 2021, 'OBMD': 2021, 'AVIA': 2021, 'IPPE': 2021, 'NASI': 2021, 'BSML': 2021, 'DRMA': 2021, 'ADMR': 2022, 'SEMA': 2022, 'ASLC': 2022, 'NETV': 2022, 'BAUT': 2022, 'ENAK': 2022, 'NTBK': 2022, 'SMKM': 2022, 'STAA': 2022, 'NANO': 2022, 'BIKE': 2022, 'WIRG': 2022, 'SICO': 2022, 'GOTO': 2022, 'TLDN': 2022, 'MTMH': 2022, 'WINR': 2022, 'IBOS': 2022, 'OLIV': 2022, 'ASHA': 2022, 'SWID': 2022, 'TRGU': 2022, 'ARKO': 2022, 'CHEM': 2022, 'DEWI': 2022, 'AXIO': 2022, 'KRYA': 2022, 'HATM': 2022, 'RCCC': 2022, 'GULA': 2022, 'JARR': 2022, 'AMMS': 2022, 'RAFI': 2022, 'KKES': 2022, 'ELPI': 2022, 'EURO': 2022, 'KLIN': 2022, 'TOOL': 2022, 'BUAH': 2022, 'CRAB': 2022, 'MEDS': 2022, 'COAL': 2022, 'PRAY': 2022, 'CBUT': 2022, 'BELI': 2022, 'MKTR': 2022, 'OMED': 2022, 'BSBK': 2022, 'PDPP': 2022, 'KDTN': 2022, 'ZATA': 2022, 'NINE': 2022, 'MMIX': 2022, 'PADA': 2022, 'ISAP': 2022, 'VTNY': 2022, 'SOUL': 2023, 'ELIT': 2023, 'BEER': 2023, 'CBPE': 2023, 'SUNI': 2023, 'CBRE': 2023, 'WINE': 2023, 'BMBL': 2023, 'PEVE': 2023, 'LAJU': 2023, 'FWCT': 2023, 'NAYZ': 2023, 'IRSX': 2023, 'PACK': 2023, 'VAST': 2023, 'CHIP': 2023, 'HALO': 2023, 'KING': 2023, 'PGEO': 2023, 'FUTR': 2023, 'HILL': 2023, 'BDKR': 2023, 'PTMP': 2023, 'SAGE': 2023, 'TRON': 2023, 'CUAN': 2023, 'NSSS': 2023, 'GTRA': 2023, 'HAJJ': 2023, 'PIPA': 2023, 'NCKL': 2023, 'MENN': 2023, 'AWAN': 2023, 'MBMA': 2023, 'RAAM': 2023, 'DOOH': 2023, 'JATI': 2023, 'TYRE': 2023, 'MPXL': 2023, 'SMIL': 2023, 'KLAS': 2023, 'MAXI': 2023, 'VKTR': 2023, 'RELF': 2023, 'AMMN': 2023, 'CRSN': 2023, 'GRPM': 2023, 'WIDI': 2023, 'TGUK': 2023, 'INET': 2023, 'MAHA': 2023, 'RMKO': 2023, 'CNMA': 2023, 'FOLK': 2023, 'HBAT': 2023, 'GRIA': 2023, 'PPRI': 2023, 'ERAL': 2023, 'CYBR': 2023, 'MUTU': 2023, 'LMAX': 2023, 'HUMI': 2023, 'MSIE': 2023, 'RSCH': 2023, 'BABY': 2023, 'AEGS': 2023, 'IOTF': 2023, 'KOCI': 2023, 'PTPS': 2023, 'BREN': 2023, 'STRK': 2023, 'KOKA': 2023, 'LOPI': 2023, 'UDNG': 2023, 'RGAS': 2023, 'MSTI': 2023, 'IKPM': 2023, 'AYAM': 2023, 'SURI': 2023, 'ASLI': 2024, 'CGAS': 2024, 'NICE': 2024, 'MSJA': 2024, 'SMLE': 2024, 'ACRO': 2024, 'MANG': 2024, 'GRPH': 2024, 'SMGA': 2024, 'UNTD': 2024, 'TOSK': 2024, 'MPIX': 2024, 'ALII': 2024, 'MKAP': 2024, 'MEJA': 2024, 'LIVE': 2024, 'HYGN': 2024, 'BAIK': 2024, 'VISI': 2024, 'AREA': 2024, 'MHKI': 2024, 'ATLA': 2024, 'DATA': 2024, 'SOLA': 2024, 'BATR': 2024, 'SPRE': 2024, 'PART': 2024, 'GOLF': 2024, 'ISEA': 2024, 'BLES': 2024, 'GUNA': 2024, 'LABS': 2024, 'DOSS': 2024, 'NEST': 2024, 'PTMR': 2024, 'VERN': 2024, 'DAAZ': 2024, 'BOAT': 2024, 'NAIK': 2024, 'AADI': 2024, 'MDIY': 2024, 'KSIX': 2025, 'RATU': 2025, 'YOII': 2025, 'HGII': 2025, 'BRRC': 2025, 'DGWG': 2025, 'CBDK': 2025, 'OBAT': 2025, 'MINE': 2025, 'ASPR': 2025, 'PSAT': 2025, 'COIN': 2025, 'CDIA': 2025, 'BLOG': 2025, 'MERI': 2025, 'CHEK': 2025, 'PMUI': 2025, 'EMAS': 2025, 'KAQI': 2025, 'YUPI': 2025, 'FORE': 2025, 'MDLA': 2025, 'DKHH': 2025, 'AYLS': 2020, 'DADA': 2020, 'ASPI': 2020, 'ESTA': 2020, 'BESS': 2020, 'AMAN': 2020, 'CARE': 2020}
# pastikan key di dict cocok dengan format ticker (4 huruf uppercase)
df_findat_final["IPO Year"] = (
    df_findat_final["ticker"]
    .map(dict_perusahaan_tahun_ipo)
    .astype("Int64") # nullable integer kalau ada ticker yang tidak ketemu di dict
)

df_findat_final[["ticker", "Year", "IPO Year"]].head()
variable ticker Year IPO Year
17 AALI 2024 1997
18 AALI 2023 1997
19 AALI 2022 1997
20 AALI 2021 1997
21 AALI 2020 1997
missing_tickers = (
    df_findat_final.loc[df_findat_final["IPO Year"].isna(), "ticker"]
    .dropna()
    .unique()
)

if len(missing_tickers) > 0:
    raise ValueError(
        f"Ada perusahaan yang tidak punya tahun IPO di dict_perusahaan_tahun_ipo: "
        f"{', '.join(sorted(map(str, missing_tickers)))}"
    )

Hasil Load Data Finansial

df_findat_final.sample(5)
variable ticker Year Entity Name 1st Level Primary Industry Year Established Date Established MM/dd/yyyy Sector Industry Group Parent Percent Owned (%) Percent Owned - All Institutions (%) Percent Owned - Insiders (%) Total Liabilities (Rp.M) Total Assets (Rp.M) Current Ratio (x) Quick Ratio (x) Working Capital (Rp.M) Total Current Assets (Rp.M) Total Current Liabilities (Rp.M) Inventory (Rp.M) Prepaid Exp. (Rp.M) Long-term Debt (Rp.M) Short-term Borrowings (Rp.M) Current Portion of LT Debt & Leases (Rp.M) Total Debt (Rp.M) Total Equity (Rp.M) Net Property, Plant & Equipment (Rp.M) Cost Of Goods Sold (Rp.M) Total Revenue (Rp.M) Operating Income (Rp.M) EBITDA (Rp.M) EBIT (Rp.M) Net Income to Company (Rp.M) Cash from Ops. (Rp.M) Net Change in Cash (Rp.M) ECS Total Common Shares Outstanding (actual) Market Capitalization (Rp.M) Year Close Stock Price (Rp.) Total Capital (Rp.M) Cash & Short-term Investments (Rp.M) Net Intangibles (Rp.M) IPO Year
1609 BAYU 2013 PT Bayu Buana Tbk (IDX:BAYU) Consumer 1972.000000 1972-10-17 00:00:00 Consumer Discretionary Consumer Services NaN 34.670000 NaN 181911.296000 453681.364000 1.444000 1.324000 101215.968000 329164.065000 227948.097000 NaN 6822.698000 NaN NaN NaN 0.000000 221787.103000 20548.433000 1382666.226000 1469958.432000 20998.587000 24942.392000 20998.587000 21104.422000 66059.556000 64460.340000 353220780.000000 141288.312000 400.000000 221787.103000 205843.369000 NaN 1989
12121 RANC 2024 PT Supra Boga Lestari Tbk (IDX:RANC) Consumer 1997.000000 1997-05-28 00:00:00 Consumer Staples Consumer Staples Distribution and Retail 70.560000 NaN 0.110000 875045.588000 1197109.086000 0.892000 0.207000 -65606.887000 541365.814000 606972.701000 399459.356000 12241.835000 NaN 60000.000000 NaN 370451.007000 322063.498000 543896.928000 2186574.396000 2874068.978000 -42324.176000 28033.186000 -42324.176000 26698.165000 77998.345000 -16938.505000 1564487500.000000 735309.125000 470.000000 692514.505000 106107.689000 10329.092000 2012
5224 FASW 2019 PT Fajar Surya Wisesa Tbk (IDX:FASW) Materials 1987.000000 1987-06-13 00:00:00 Materials Materials 99.700000 NaN NaN 6059395.121000 10751992.944000 0.704000 0.369000 -1110259.102000 2641761.194000 3752020.296000 1094392.608000 38441.889000 1567244.621000 1692335.200000 NaN 4248225.014000 4692597.823000 7847119.796000 6459851.090000 8268503.880000 1369618.341000 1598327.786000 1369618.341000 968833.391000 1116219.496000 -253971.195000 2477888787.000000 19079743.659900 7700.000000 8940822.837000 101255.876000 1472.464000 1994
9911 MSIE 2024 PT Multisarana Intan Eduka Tbk (IDX:MSIE) Real Estate 2011.000000 2011-07-30 00:00:00 Real Estate Real Estate Management and Development NaN NaN 75.430000 18596.055000 131321.384000 1.108000 0.774000 1078.118000 11062.096000 9983.978000 NaN NaN 2405.192000 1751.469000 NaN 7953.600000 112725.330000 115853.389000 2165.145000 6414.263000 -1120.784000 -82.906000 -1120.784000 -301.893000 6808.031000 -20330.500000 1460007754.000000 18980.100802 13.000000 120678.929000 5394.008000 2405.900000 2023
5092 ESSA 2015 PT ESSA Industries Indonesia Tbk. (IDX:ESSA) Materials 2006.000000 2006-03-24 01:00:00 Materials Materials NaN 14.520000 38.100000 1307723.953939 3834551.707532 0.816000 0.471000 -106312.815469 470937.843858 577250.659327 14021.277761 6627.861245 715744.356775 NaN NaN 739219.623158 2526827.753593 1196028.348241 313956.026525 542945.657748 117129.147409 197604.250616 117129.147409 65297.007445 -87070.568237 574.780607 11000000000.000000 1815000.000000 165.000000 3266047.376751 155674.162119 NaN 2012

Perbaikan Data Finansial

Cek Kesesuaian Tipe Data Kolom

numeric_cols = df_findat_final.select_dtypes(include=['number']).columns.tolist()
categorical_cols = df_findat_final.select_dtypes(include=['object']).columns.tolist()

print("Numeric columns:")
print(numeric_cols)
print("\nCategorical columns:")
print(categorical_cols)
Numeric columns:
['Year', 'Year Established', 'Parent Percent Owned (%)', 'Percent Owned - All Institutions (%)', 'Percent Owned - Insiders (%)', 'Total Liabilities (Rp.M)', 'Total Assets (Rp.M)', 'Current Ratio (x)', 'Quick Ratio (x)', 'Working Capital (Rp.M)', 'Total Current Assets (Rp.M)', 'Total Current Liabilities (Rp.M)', 'Inventory (Rp.M)', 'Prepaid Exp. (Rp.M)', 'Long-term Debt (Rp.M)', 'Short-term Borrowings (Rp.M)', 'Current Portion of LT Debt & Leases (Rp.M)', 'Total Debt (Rp.M)', 'Total Equity (Rp.M)', 'Net Property, Plant & Equipment (Rp.M)', 'Cost Of Goods Sold (Rp.M)', 'Total Revenue (Rp.M)', 'Operating Income (Rp.M)', 'EBITDA (Rp.M)', 'EBIT (Rp.M)', 'Net Income to Company (Rp.M)', 'Cash from Ops. (Rp.M)', 'Net Change in Cash (Rp.M)', 'ECS Total Common Shares Outstanding (actual)', 'Market Capitalization (Rp.M)', 'Year Close Stock Price (Rp.)', 'Total Capital (Rp.M)', 'Cash & Short-term Investments (Rp.M)', 'Net Intangibles (Rp.M)', 'IPO Year']

Categorical columns:
['ticker', 'Entity Name', '1st Level Primary Industry', 'Date Established MM/dd/yyyy', 'Sector', 'Industry Group']
print('Cek apakah Date Established sudah berformat Datetime:')
print(pd.api.types.is_datetime64_any_dtype(df_findat_final['Date Established MM/dd/yyyy']))
df_findat_final['Date Established MM/dd/yyyy'].head(3)
Cek apakah Date Established sudah berformat Datetime:
False
Date Established MM/dd/yyyy
17 1988-10-03 00:00:00
18 1988-10-03 00:00:00
19 1988-10-03 00:00:00

print('Konversi Date Established ke Datetime dan Ubah Nama Kolom:')
df_findat_final['Date Established MM/dd/yyyy'] = pd.to_datetime(df_findat_final['Date Established MM/dd/yyyy'], errors="coerce")
df_findat_final = df_findat_final.rename(columns={'Date Established MM/dd/yyyy': 'Date Established'})
df_findat_final['Date Established'].head(3)
Konversi Date Established ke Datetime dan Ubah Nama Kolom:
Date Established
17 1988-10-03
18 1988-10-03
19 1988-10-03

Cek Null yg Bisa Dihitung Manual

Total Aset = Total Ekuitas + Liabilitas

cols_to_check = [
    'Total Assets (Rp.M)',
    'Total Equity (Rp.M)',
    'Total Liabilities (Rp.M)'
]

# Cek yang bisa diisi
null_counts_before = df_findat_final[cols_to_check].isnull().sum(axis=1)
rows_target = (null_counts_before == 1).sum()
print(f"Jumlah baris yang akan diperbaiki (tepat 1 null): {rows_target}")
if rows_target == 0:
    print("Tidak ada baris yang bisa diperbaiki.")
Jumlah baris yang akan diperbaiki (tepat 1 null): 0
Tidak ada baris yang bisa diperbaiki.

Market Cap = Harga x Jumlah Saham

Dari ketiga kolom tersebut, jika yang null hanya 1 maka dapat dihitung.

cols_to_check = [
    'ECS Total Common Shares Outstanding (actual)',
    'Market Capitalization (Rp.M)',
    'Year Close Stock Price (Rp.)'
]

col_shares = 'ECS Total Common Shares Outstanding (actual)'
col_mcap   = 'Market Capitalization (Rp.M)'
col_price  = 'Year Close Stock Price (Rp.)'

# 1. Cek jumlah null awal
null_counts_before = df_findat_final[cols_to_check].isnull().sum(axis=1)
rows_target = (null_counts_before == 1).sum()
print(f"Jumlah baris yang akan diperbaiki (tepat 1 null): {rows_target}")

# ---------------------------------------------------------
# PROSES PENGISIAN (IMPUTASI) DENGAN RUMUS
# ---------------------------------------------------------

# KASUS A: Shares Kosong -> Hitung pakai (Market Cap * 1jt) / Price
# Syarat: Shares IS NULL, tapi Market Cap & Price TIDAK NULL
mask_shares_null = (
    df_findat_final[col_shares].isnull() &
    df_findat_final[col_mcap].notnull() &
    df_findat_final[col_price].notnull()
)
df_findat_final.loc[mask_shares_null, col_shares] = (
    df_findat_final.loc[mask_shares_null, col_mcap] * 1_000_000
) / df_findat_final.loc[mask_shares_null, col_price]


# KASUS B: Market Cap Kosong -> Hitung pakai (Price * Shares) / 1jt
# Syarat: Market Cap IS NULL, tapi Price & Shares TIDAK NULL
mask_mcap_null = (
    df_findat_final[col_mcap].isnull() &
    df_findat_final[col_price].notnull() &
    df_findat_final[col_shares].notnull()
)
df_findat_final.loc[mask_mcap_null, col_mcap] = (
    df_findat_final.loc[mask_mcap_null, col_price] * df_findat_final.loc[mask_mcap_null, col_shares]
) / 1_000_000


# KASUS C: Price Kosong -> Hitung pakai (Market Cap * 1jt) / Shares
# Syarat: Price IS NULL, tapi Market Cap & Shares TIDAK NULL
mask_price_null = (
    df_findat_final[col_price].isnull() &
    df_findat_final[col_mcap].notnull() &
    df_findat_final[col_shares].notnull()
)
df_findat_final.loc[mask_price_null, col_price] = (
    df_findat_final.loc[mask_price_null, col_mcap] * 1_000_000
) / df_findat_final.loc[mask_price_null, col_shares]

# ---------------------------------------------------------

# Verifikasi Hasil
null_counts_after = df_findat_final[cols_to_check].isnull().sum(axis=1)
rows_remaining = (null_counts_after == 1).sum()
print(f"Sisa baris dengan 1 null setelah perbaikan: {rows_remaining} (Harus 0)")
Jumlah baris yang akan diperbaiki (tepat 1 null): 38
Sisa baris dengan 1 null setelah perbaikan: 0 (Harus 0)

Working Capital = CA - CL

cols_to_check = [
    'Working Capital (Rp.M)',
    'Total Current Assets (Rp.M)',
    'Total Current Liabilities (Rp.M)'
]

# Cek jumlah null tepat 1
null_counts_before = df_findat_final[cols_to_check].isnull().sum(axis=1)
rows_target = (null_counts_before == 1).sum()
print(f"Jumlah baris yang dapat diperbaiki (tepat 1 null): {rows_target}")
Jumlah baris yang dapat diperbaiki (tepat 1 null): 21
df_findat_final[df_findat_final[cols_to_check].isna().sum(axis=1)==1][cols_to_check]
variable Working Capital (Rp.M) Total Current Assets (Rp.M) Total Current Liabilities (Rp.M)
3011 NaN 303261.676000 101907.258000
3012 NaN 233819.275000 67363.137000
3013 NaN 194827.419000 41344.686000
3014 NaN 189163.251000 41766.037000
3015 NaN 176074.194000 30419.619000
3016 NaN 138161.400000 25235.541000
3017 NaN 128801.476000 30523.107000
3019 NaN 125563.723000 24837.582000
3020 NaN 126889.991000 34947.793000
5731 NaN 88045.362000 29098.404000
5732 NaN 112647.222000 31756.190000
5733 NaN 110339.347000 25792.856000
5734 NaN 114100.749000 31386.202000
5738 NaN 78360.594000 10766.730000
5739 NaN 78937.813000 8735.836000
5740 NaN 78380.255000 13497.036000
9879 NaN 586852.139000 236276.100000
9880 NaN 459338.630000 215622.712000
9881 NaN 432576.455000 195801.413000
9892 NaN 279386.668000 38918.133000
9893 NaN 274498.610000 43498.273000
# Cek apakah benar working capital nilainya sama dengan total current assets dikurangi total current liabilities
wc  = "Working Capital (Rp.M)"
tca = "Total Current Assets (Rp.M)"
tcl = "Total Current Liabilities (Rp.M)"

# hanya cek baris yang lengkap (tidak null)
mask = df_findat_final[[wc, tca, tcl]].notna().all(axis=1)

calc = df_findat_final[tca] - df_findat_final[tcl]
diff = (df_findat_final.loc[mask, wc] - calc.loc[mask]).abs()

print("Jumlah baris dicek:", int(mask.sum()))
print("Jumlah yang sama persis:", int((diff == 0).sum()))
print("Jumlah yang beda:", int((diff != 0).sum()))
print("Max abs diff:", float(diff.max()))
print("Median abs diff:", float(diff.median()))
Jumlah baris dicek: 8468
Jumlah yang sama persis: 4134
Jumlah yang beda: 4334
Max abs diff: 0.0010000020265579224
Median abs diff: 1.8189894035458565e-12
# Hitung WC = CA - CL
wc_calculated = df_findat_final['Total Current Assets (Rp.M)'] - df_findat_final['Total Current Liabilities (Rp.M)']

# Tentukan baris 'Working Capital (Rp.M)' null
# DAN kedua kolom 'Total Current Assets (Rp.M)' & 'Total Current Liabilities (Rp.M)' tidak null
mask_to_fill = (
    df_findat_final['Working Capital (Rp.M)'].isnull() &
    df_findat_final['Total Current Assets (Rp.M)'].notnull() &
    df_findat_final['Total Current Liabilities (Rp.M)'].notnull()
)

# Isi null
df_findat_final.loc[mask_to_fill, 'Working Capital (Rp.M)'] = wc_calculated[mask_to_fill]

print("Null values di 'Working Capital (Rp.M)' yang memungkinkan diisi berhasil diisi.")
print(f"Jumlah baris yang diimputasi dengan wc_calculated: {mask_to_fill.sum()}")
print(f"Jumlah null di 'Working Capital (Rp.M)' setelah proses: {df_findat_final['Working Capital (Rp.M)'].isnull().sum()}")
Null values di 'Working Capital (Rp.M)' yang memungkinkan diisi berhasil diisi.
Jumlah baris yang diimputasi dengan wc_calculated: 21
Jumlah null di 'Working Capital (Rp.M)' setelah proses: 60

Total Debt = LTD + STB + CPLTD

df_findat_final[(df_findat_final['Total Debt (Rp.M)'].isna()) &
                (df_findat_final['Short-term Borrowings (Rp.M)'].notna()) &
                (df_findat_final['Current Portion of LT Debt & Leases (Rp.M)'].notna()) &
                (df_findat_final['Long-term Debt (Rp.M)'].notna())
                ]
# Tidak ada yang bisa diisi dengan dihitung
variable ticker Year Entity Name 1st Level Primary Industry Year Established Date Established Sector Industry Group Parent Percent Owned (%) Percent Owned - All Institutions (%) Percent Owned - Insiders (%) Total Liabilities (Rp.M) Total Assets (Rp.M) Current Ratio (x) Quick Ratio (x) Working Capital (Rp.M) Total Current Assets (Rp.M) Total Current Liabilities (Rp.M) Inventory (Rp.M) Prepaid Exp. (Rp.M) Long-term Debt (Rp.M) Short-term Borrowings (Rp.M) Current Portion of LT Debt & Leases (Rp.M) Total Debt (Rp.M) Total Equity (Rp.M) Net Property, Plant & Equipment (Rp.M) Cost Of Goods Sold (Rp.M) Total Revenue (Rp.M) Operating Income (Rp.M) EBITDA (Rp.M) EBIT (Rp.M) Net Income to Company (Rp.M) Cash from Ops. (Rp.M) Net Change in Cash (Rp.M) ECS Total Common Shares Outstanding (actual) Market Capitalization (Rp.M) Year Close Stock Price (Rp.) Total Capital (Rp.M) Cash & Short-term Investments (Rp.M) Net Intangibles (Rp.M) IPO Year

Total Capital = Total Equity + Total Debt

jml_bisa_diisi = len(df_findat_final[(df_findat_final['Total Capital (Rp.M)'].isna()) &
                      (df_findat_final['Total Equity (Rp.M)'].notna()) &
                      (df_findat_final['Total Debt (Rp.M)'].notna())
                    ])
print(f"Jumlah baris yang bisa diisi: {jml_bisa_diisi}")
Jumlah baris yang bisa diisi: 669
tc = "Total Capital (Rp.M)"
te = "Total Equity (Rp.M)"
td = "Total Debt (Rp.M)"

tc_calc = df_findat_final[te] + df_findat_final[td]

mask = df_findat_final[[tc, te, td]].notna().all(axis=1)

diff = df_findat_final.loc[mask, tc] - tc_calc.loc[mask]
abs_diff = diff.abs()

# error relatif (hindari pembagian 0)
den = df_findat_final.loc[mask, tc].abs().replace(0, np.nan)
rel_err = abs_diff / den

print("n dibandingkan:", int(mask.sum()))
print("match persis:", int((diff == 0).sum()), "| prop:", float((diff == 0).mean()))
print("median abs diff:", float(abs_diff.median()))
print("mean abs diff:", float(abs_diff.mean()))
print("median rel err:", float(rel_err.median()))
n dibandingkan: 7799
match persis: 5571 | prop: 0.714322349019105
median abs diff: 0.0
mean abs diff: 0.00011411735155438838
median rel err: 0.0
# Isi Total Capital dengan rumus TE + TD

mask_fill = (
    df_findat_final["Total Capital (Rp.M)"].isna() &
    df_findat_final[["Total Equity (Rp.M)", "Total Debt (Rp.M)"]].notna().all(axis=1)
)

df_findat_final.loc[mask_fill, "Total Capital (Rp.M)"] = (
    df_findat_final.loc[mask_fill, "Total Equity (Rp.M)"] +
    df_findat_final.loc[mask_fill, "Total Debt (Rp.M)"]
)
print('Sisa null Total Capital sekarang : ', df_findat_final['Total Capital (Rp.M)'].isna().sum())
Sisa null Total Capital sekarang :  81

Year Established

print('Jumlah baris yang Year Established-nya null:', df_findat_final['Year Established'].isna().sum())
Jumlah baris yang Year Established-nya null: 21
print('Cek baris dimana Year Established null tapi Date Established ada')
display(df_findat_final[(df_findat_final['Year Established'].isna()) &
                (df_findat_final['Date Established'].notna())])

if (len(df_findat_final[(df_findat_final['Year Established'].isna()) &
                (df_findat_final['Date Established'].notna())]) == 0):
    print('Tidak ada Year Established kosong yang bisa diisi dengan Date Established')
Cek baris dimana Year Established null tapi Date Established ada
variable ticker Year Entity Name 1st Level Primary Industry Year Established Date Established Sector Industry Group Parent Percent Owned (%) Percent Owned - All Institutions (%) Percent Owned - Insiders (%) Total Liabilities (Rp.M) Total Assets (Rp.M) Current Ratio (x) Quick Ratio (x) Working Capital (Rp.M) Total Current Assets (Rp.M) Total Current Liabilities (Rp.M) Inventory (Rp.M) Prepaid Exp. (Rp.M) Long-term Debt (Rp.M) Short-term Borrowings (Rp.M) Current Portion of LT Debt & Leases (Rp.M) Total Debt (Rp.M) Total Equity (Rp.M) Net Property, Plant & Equipment (Rp.M) Cost Of Goods Sold (Rp.M) Total Revenue (Rp.M) Operating Income (Rp.M) EBITDA (Rp.M) EBIT (Rp.M) Net Income to Company (Rp.M) Cash from Ops. (Rp.M) Net Change in Cash (Rp.M) ECS Total Common Shares Outstanding (actual) Market Capitalization (Rp.M) Year Close Stock Price (Rp.) Total Capital (Rp.M) Cash & Short-term Investments (Rp.M) Net Intangibles (Rp.M) IPO Year
Tidak ada Year Established kosong yang bisa diisi dengan Date Established

Year Established tidak bisa diisi dengan Date Established, maka drop Date Established

# Drop Date Established
df_findat_final = df_findat_final.drop(columns=['Date Established'])
print('Perusahaan yang punya baris dengan Year Established null:')
perusahaan_dengan_baris_year_established_null = df_findat_final[df_findat_final['Year Established'].isna()]['Entity Name'].unique().tolist()
perusahaan_dengan_baris_year_established_null
Perusahaan yang punya baris dengan Year Established null:
['PT Ashmore Asset Management Indonesia Tbk (IDX:AMOR)',
 'PT Bank Syariah Indonesia Tbk (IDX:BRIS)',
 'PT Saratoga Investama Sedaya Tbk (IDX:SRTG)']
print('Baris Year Established null:')
df_findat_final[df_findat_final['Year Established'].isna()][['Entity Name','Year', 'Year Established']]
Baris Year Established null:
variable Entity Name Year Year Established
663 PT Ashmore Asset Management Indonesia Tbk (IDX... 2024 NaN
664 PT Ashmore Asset Management Indonesia Tbk (IDX... 2023 NaN
665 PT Ashmore Asset Management Indonesia Tbk (IDX... 2022 NaN
666 PT Ashmore Asset Management Indonesia Tbk (IDX... 2021 NaN
2805 PT Bank Syariah Indonesia Tbk (IDX:BRIS) 2024 NaN
2806 PT Bank Syariah Indonesia Tbk (IDX:BRIS) 2023 NaN
2807 PT Bank Syariah Indonesia Tbk (IDX:BRIS) 2022 NaN
2808 PT Bank Syariah Indonesia Tbk (IDX:BRIS) 2021 NaN
2809 PT Bank Syariah Indonesia Tbk (IDX:BRIS) 2020 NaN
2810 PT Bank Syariah Indonesia Tbk (IDX:BRIS) 2019 NaN
13515 PT Saratoga Investama Sedaya Tbk (IDX:SRTG) 2024 NaN
13516 PT Saratoga Investama Sedaya Tbk (IDX:SRTG) 2023 NaN
13517 PT Saratoga Investama Sedaya Tbk (IDX:SRTG) 2022 NaN
13518 PT Saratoga Investama Sedaya Tbk (IDX:SRTG) 2021 NaN
13519 PT Saratoga Investama Sedaya Tbk (IDX:SRTG) 2020 NaN
13520 PT Saratoga Investama Sedaya Tbk (IDX:SRTG) 2019 NaN
13521 PT Saratoga Investama Sedaya Tbk (IDX:SRTG) 2018 NaN
13522 PT Saratoga Investama Sedaya Tbk (IDX:SRTG) 2017 NaN
13523 PT Saratoga Investama Sedaya Tbk (IDX:SRTG) 2016 NaN
13524 PT Saratoga Investama Sedaya Tbk (IDX:SRTG) 2015 NaN
13525 PT Saratoga Investama Sedaya Tbk (IDX:SRTG) 2014 NaN
print('Cek apakah bisa diisi dengan baris lain\n')
df_findat_final[df_findat_final['Entity Name'].isin(perusahaan_dengan_baris_year_established_null)][['Entity Name','Year', 'Year Established']]
Cek apakah bisa diisi dengan baris lain
variable Entity Name Year Year Established
663 PT Ashmore Asset Management Indonesia Tbk (IDX... 2024 NaN
664 PT Ashmore Asset Management Indonesia Tbk (IDX... 2023 NaN
665 PT Ashmore Asset Management Indonesia Tbk (IDX... 2022 NaN
666 PT Ashmore Asset Management Indonesia Tbk (IDX... 2021 NaN
2805 PT Bank Syariah Indonesia Tbk (IDX:BRIS) 2024 NaN
2806 PT Bank Syariah Indonesia Tbk (IDX:BRIS) 2023 NaN
2807 PT Bank Syariah Indonesia Tbk (IDX:BRIS) 2022 NaN
2808 PT Bank Syariah Indonesia Tbk (IDX:BRIS) 2021 NaN
2809 PT Bank Syariah Indonesia Tbk (IDX:BRIS) 2020 NaN
2810 PT Bank Syariah Indonesia Tbk (IDX:BRIS) 2019 NaN
13515 PT Saratoga Investama Sedaya Tbk (IDX:SRTG) 2024 NaN
13516 PT Saratoga Investama Sedaya Tbk (IDX:SRTG) 2023 NaN
13517 PT Saratoga Investama Sedaya Tbk (IDX:SRTG) 2022 NaN
13518 PT Saratoga Investama Sedaya Tbk (IDX:SRTG) 2021 NaN
13519 PT Saratoga Investama Sedaya Tbk (IDX:SRTG) 2020 NaN
13520 PT Saratoga Investama Sedaya Tbk (IDX:SRTG) 2019 NaN
13521 PT Saratoga Investama Sedaya Tbk (IDX:SRTG) 2018 NaN
13522 PT Saratoga Investama Sedaya Tbk (IDX:SRTG) 2017 NaN
13523 PT Saratoga Investama Sedaya Tbk (IDX:SRTG) 2016 NaN
13524 PT Saratoga Investama Sedaya Tbk (IDX:SRTG) 2015 NaN
13525 PT Saratoga Investama Sedaya Tbk (IDX:SRTG) 2014 NaN

Cek Kolom Tidak Boleh Negatif (Biasanya)

columns_to_check_negative = ['Total Revenue (Rp.M)', 'Market Capitalization (Rp.M)', 'Total Assets (Rp.M)']

for col in columns_to_check_negative:
    negative_count = (df_findat_final[col] < 0).sum()
    print(f"Jumlah baris dengan nilai negatif pada kolom '{col}': {negative_count}")
Jumlah baris dengan nilai negatif pada kolom 'Total Revenue (Rp.M)': 71
Jumlah baris dengan nilai negatif pada kolom 'Market Capitalization (Rp.M)': 0
Jumlah baris dengan nilai negatif pada kolom 'Total Assets (Rp.M)': 0
df_findat_final[df_findat_final['Total Revenue (Rp.M)'] < 0][['Entity Name']].value_counts()
count
Entity Name
PT Lenox Pasifik Investama Tbk (IDX:LPPS) 7
PT Intan Baru Prana Tbk (IDX:IBFN) 6
PT Bank KB Indonesia Tbk (IDX:BBKP) 5
PT Minna Padi Investama Sekuritas Tbk (IDX:PADI) 5
PT Panca Global Kapital Tbk (IDX:PEGE) 4
PT Pool Advista Finance Tbk (IDX:POLA) 4
PT Capitalinc Investment Tbk (IDX:MTFN) 3
PT Charnic Capital Tbk (IDX:NICK) 3
PT. Bank Pembangunan Daerah Banten, Tbk (IDX:BEKS) 3
PT Bank JTrust Indonesia Tbk (IDX:BCIC) 3
PT Bank QNB Indonesia Tbk (IDX:BKSW) 3
PT Provident Investasi Bersama Tbk (IDX:PALM) 2
PT Danasupra Erapacific Tbk (IDX:DEFI) 2
PT Bank Panin Dubai Syariah Tbk (IDX:PNBS) 2
PT Saratoga Investama Sedaya Tbk (IDX:SRTG) 2
PT Radana Bhaskara Finance Tbk (IDX:HDFA) 2
PT Indonesia Prima Property Tbk (IDX:OMRE) 2
PT Bank Permata Tbk (IDX:BNLI) 1
PT Bank IBK Indonesia Tbk (IDX:AGRS) 1
PT Allo Bank Indonesia Tbk (IDX:BBHI) 1
PT Bank Jago Tbk (IDX:ARTO) 1
PT Bank MNC Internasional Tbk (IDX:BABP) 1
PT Bank of India Indonesia Tbk (IDX:BSWD) 1
PT Bank Raya Indonesia Tbk (IDX:AGRO) 1
PT MNC Tourism Indonesia Tbk (IDX:KPIG) 1
PT Himalaya Energi Perkasa Tbk (IDX:HADE) 1
PT Mineral Sumberdaya Mandiri Tbk (IDX:AKSI) 1
PT Magna Investama Mandiri Tbk (IDX:MGNA) 1
PT Mizuho Leasing Indonesia Tbk (IDX:VRNA) 1
PT Yulie Sekuritas Indonesia Tbk (IDX:YULE) 1

cols = ["ticker","Year","Sector", "Industry Group",
        "Total Revenue (Rp.M)", "Operating Income (Rp.M)",
        "EBITDA (Rp.M)", "Net Income to Company (Rp.M)"]

df_findat_final.loc[df_findat_final["Total Revenue (Rp.M)"] < 0, cols] \
               .sort_values(["ticker","Year"])
variable ticker Year Sector Industry Group Total Revenue (Rp.M) Operating Income (Rp.M) EBITDA (Rp.M) Net Income to Company (Rp.M)
309 AGRO 2021 Financials Banks -2900117.934000 -3302855.412000 NaN -3045701.407000
328 AGRS 2019 Financials Banks -46978.000000 -270402.000000 NaN -248836.000000
455 AKSI 2011 Industrials Transportation -29020.539000 -38088.940000 -37466.047000 -36437.585000
991 ARTO 2019 Financials Banks -48665.000000 -91150.000000 NaN -121966.000000
1401 BABP 2017 Financials Banks -349818.000000 -886730.000000 NaN -685193.000000
... ... ... ... ... ... ... ... ...
11424 POLA 2024 Financials Financial Services -5408.036000 -29851.381000 NaN -28745.813000
13521 SRTG 2018 Financials Financial Services -6089443.000000 -6313642.000000 -6312597.000000 -6134832.000000
13516 SRTG 2023 Financials Financial Services -10991269.000000 -11211922.000000 -11210658.000000 -10151341.000000
15051 VRNA 2018 Financials Financial Services -125436.117000 -202662.629000 NaN -192758.906000
15593 YULE 2020 Financials Financial Services -1657.122000 -10844.380000 NaN -7225.945000

71 rows × 8 columns

Mayoritas perusahaan finansial dan Net Income memang negatif, sehingga sesuai dan tetap dipertahankan.

Cek Zero Values

zero_values_summary = {}

for col in df_findat_final.select_dtypes(include=np.number).columns:
    zero_count = (df_findat_final[col] == 0).sum()
    if zero_count > 0:
        percentage = (zero_count / len(df_findat_final)) * 100
        zero_values_summary[col] = {
            "count": zero_count,
            "percentage": f"{percentage:.2f}%"
        }

if zero_values_summary:
    print("Columns in df_findat_final with zero values:")
    for col_name, data in zero_values_summary.items():
        print(f"- {col_name}: {data['count']} zeros ({data['percentage']})")
else:
    print("No numeric columns in df_findat_final contain zero values.")
Columns in df_findat_final with zero values:
- Percent Owned - All Institutions (%): 201 zeros (2.35%)
- Percent Owned - Insiders (%): 630 zeros (7.37%)
- Current Ratio (x): 2 zeros (0.02%)
- Quick Ratio (x): 2 zeros (0.02%)
- Inventory (Rp.M): 13 zeros (0.15%)
- Total Debt (Rp.M): 804 zeros (9.40%)
- Net Property, Plant & Equipment (Rp.M): 7 zeros (0.08%)

Cek Kolom Seharusnya Tidak Kosong

cols_to_impute = [
    "Net Property, Plant & Equipment (Rp.M)", "Total Revenue (Rp.M)",
    "Cash from Ops. (Rp.M)", "Net Change in Cash (Rp.M)", "Total Assets (Rp.M)"
]

for col in cols_to_impute:
    print(f"Jumlah Null pada {col}: {df_findat_final[col].isnull().sum()}")
Jumlah Null pada Net Property, Plant & Equipment (Rp.M): 79
Jumlah Null pada Total Revenue (Rp.M): 91
Jumlah Null pada Cash from Ops. (Rp.M): 62
Jumlah Null pada Net Change in Cash (Rp.M): 62
Jumlah Null pada Total Assets (Rp.M): 60

Jika perlu imputasi, imputasi dilakukan di tahap setelah feature engineering agar dataset yang mempertahankan null bisa dibuat fitur-fitur raisonya terlebih dahulu.

Cek Null

Jumlah Null Tiap Kolom

null_counts = df_findat_final.isnull().sum()
total_rows = len(df_findat_final)
null_percentages = (null_counts / total_rows) * 100

null_summary_df = pd.DataFrame({
    'Null Count': null_counts,
    'Null Percentage': null_percentages
})

null_summary_df = null_summary_df.sort_values(by='Null Count', ascending=False)

print("Summary of Null Values in df_findat_final:")
print(null_summary_df.to_string())
Summary of Null Values in df_findat_final:
                                              Null Count  Null Percentage
variable                                                                 
Current Portion of LT Debt & Leases (Rp.M)          8212        96.058018
Net Intangibles (Rp.M)                              6786        79.377705
Percent Owned - All Institutions (%)                3983        46.590244
Parent Percent Owned (%)                            3681        43.057668
Short-term Borrowings (Rp.M)                        2970        34.740905
Long-term Debt (Rp.M)                               2641        30.892502
Percent Owned - Insiders (%)                        2578        30.155574
Prepaid Exp. (Rp.M)                                 1522        17.803252
Inventory (Rp.M)                                    1266        14.808750
EBITDA (Rp.M)                                       1166        13.639022
EBIT (Rp.M)                                         1159        13.557141
Current Ratio (x)                                    766         8.960112
Quick Ratio (x)                                      764         8.936718
Cost Of Goods Sold (Rp.M)                            240         2.807346
Total Revenue (Rp.M)                                  91         1.064452
Total Capital (Rp.M)                                  81         0.947479
Total Debt (Rp.M)                                     81         0.947479
Net Property, Plant & Equipment (Rp.M)                79         0.924085
Net Change in Cash (Rp.M)                             62         0.725231
Cash from Ops. (Rp.M)                                 62         0.725231
Operating Income (Rp.M)                               61         0.713534
Working Capital (Rp.M)                                60         0.701836
Total Current Assets (Rp.M)                           60         0.701836
Total Assets (Rp.M)                                   60         0.701836
Net Income to Company (Rp.M)                          60         0.701836
Total Equity (Rp.M)                                   60         0.701836
Cash & Short-term Investments (Rp.M)                  60         0.701836
Total Current Liabilities (Rp.M)                      60         0.701836
Total Liabilities (Rp.M)                              58         0.678442
Market Capitalization (Rp.M)                          52         0.608258
Year Close Stock Price (Rp.)                          52         0.608258
ECS Total Common Shares Outstanding (actual)          28         0.327524
Year Established                                      21         0.245643
Sector                                                 0         0.000000
Industry Group                                         0         0.000000
1st Level Primary Industry                             0         0.000000
Year                                                   0         0.000000
ticker                                                 0         0.000000
Entity Name                                            0         0.000000
IPO Year                                               0         0.000000

Jumlah Baris untuk tiap Jumlah Null

# hitung frekuensi jumlah null per baris
print('Jumlah baris untuk tiap jumlah null:')
hasil_null_per_baris = df_findat_final.isnull().sum(axis=1).value_counts().sort_index()
print(hasil_null_per_baris)
Jumlah baris untuk tiap jumlah null:
1      236
2     1057
3     1766
4     1958
5     1483
6      877
7      468
8      294
9      210
10      95
11      26
12      18
13       1
26       4
27       8
28      16
29       9
30       8
31       7
32       8
Name: count, dtype: int64
print('Jumlah baris dengan null >25 :')
df_findat_final[df_findat_final.isnull().sum(axis=1) > 25]
Jumlah baris dengan null >25 :
variable ticker Year Entity Name 1st Level Primary Industry Year Established Sector Industry Group Parent Percent Owned (%) Percent Owned - All Institutions (%) Percent Owned - Insiders (%) Total Liabilities (Rp.M) Total Assets (Rp.M) Current Ratio (x) Quick Ratio (x) Working Capital (Rp.M) Total Current Assets (Rp.M) Total Current Liabilities (Rp.M) Inventory (Rp.M) Prepaid Exp. (Rp.M) Long-term Debt (Rp.M) Short-term Borrowings (Rp.M) Current Portion of LT Debt & Leases (Rp.M) Total Debt (Rp.M) Total Equity (Rp.M) Net Property, Plant & Equipment (Rp.M) Cost Of Goods Sold (Rp.M) Total Revenue (Rp.M) Operating Income (Rp.M) EBITDA (Rp.M) EBIT (Rp.M) Net Income to Company (Rp.M) Cash from Ops. (Rp.M) Net Change in Cash (Rp.M) ECS Total Common Shares Outstanding (actual) Market Capitalization (Rp.M) Year Close Stock Price (Rp.) Total Capital (Rp.M) Cash & Short-term Investments (Rp.M) Net Intangibles (Rp.M) IPO Year
527 ALTO 2024 PT Tri Banyan Tirta Tbk (IDX:ALTO) Consumer 1997.000000 Consumer Staples Food, Beverage and Tobacco 51.090000 16.440000 2.240000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2191870558.000000 35069.928928 16.000000 NaN NaN NaN 2012
1904 BEBS 2024 PT Berkah Beton Sadaya Tbk (IDX:BEBS) Materials 2019.000000 Materials Materials NaN 5.810000 4.510000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 45000000000.000000 225000.000000 5.000000 NaN NaN NaN 2021
2108 BIKA 2024 PT Binakarya Jaya Abadi Tbk (IDX:BIKA) Real Estate 2007.000000 Real Estate Real Estate Management and Development NaN NaN 72.190000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 592280000.000000 32575.400000 55.000000 NaN NaN NaN 2015
2703 BOSS 2024 PT. Borneo Olah Sarana Sukses Tbk (IDX:BOSS) Energy and Utilities 2011.000000 Energy Energy NaN NaN 0.020000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1400000000.000000 70000.000000 50.000000 NaN NaN NaN 2018
2704 BOSS 2023 PT. Borneo Olah Sarana Sukses Tbk (IDX:BOSS) Energy and Utilities 2011.000000 Energy Energy NaN NaN 0.020000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1400000000.000000 70000.000000 50.000000 NaN NaN NaN 2018
3002 BTEK 2014 PT Bumi Teknokultura Unggul Tbk (IDX:BTEK) Consumer 2001.000000 Consumer Staples Food, Beverage and Tobacco NaN 14.650000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2004
3003 BTEK 2013 PT Bumi Teknokultura Unggul Tbk (IDX:BTEK) Consumer 2001.000000 Consumer Staples Food, Beverage and Tobacco NaN 14.650000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2004
3004 BTEK 2012 PT Bumi Teknokultura Unggul Tbk (IDX:BTEK) Consumer 2001.000000 Consumer Staples Food, Beverage and Tobacco NaN 14.650000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2004
3005 BTEK 2011 PT Bumi Teknokultura Unggul Tbk (IDX:BTEK) Consumer 2001.000000 Consumer Staples Food, Beverage and Tobacco NaN 14.650000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2004
3006 BTEK 2010 PT Bumi Teknokultura Unggul Tbk (IDX:BTEK) Consumer 2001.000000 Consumer Staples Food, Beverage and Tobacco NaN 14.650000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2004
3007 BTEK 2009 PT Bumi Teknokultura Unggul Tbk (IDX:BTEK) Consumer 2001.000000 Consumer Staples Food, Beverage and Tobacco NaN 14.650000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2004
3008 BTEK 2008 PT Bumi Teknokultura Unggul Tbk (IDX:BTEK) Consumer 2001.000000 Consumer Staples Food, Beverage and Tobacco NaN 14.650000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2004
3208 BWPT 2012 PT Eagle High Plantations Tbk (IDX:BWPT) Consumer 2000.000000 Consumer Staples Food, Beverage and Tobacco NaN 0.060000 0.040000 4789012.000000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2009
3209 BWPT 2011 PT Eagle High Plantations Tbk (IDX:BWPT) Consumer 2000.000000 Consumer Staples Food, Beverage and Tobacco NaN 0.060000 0.040000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2009
3210 BWPT 2010 PT Eagle High Plantations Tbk (IDX:BWPT) Consumer 2000.000000 Consumer Staples Food, Beverage and Tobacco NaN 0.060000 0.040000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2009
3732 CMPP 2015 PT AirAsia Indonesia Tbk (IDX:CMPP) Industrials 1989.000000 Industrials Transportation NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1994
3733 CMPP 2014 PT AirAsia Indonesia Tbk (IDX:CMPP) Industrials 1989.000000 Industrials Transportation NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1994
3734 CMPP 2013 PT AirAsia Indonesia Tbk (IDX:CMPP) Industrials 1989.000000 Industrials Transportation NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1994
3735 CMPP 2012 PT AirAsia Indonesia Tbk (IDX:CMPP) Industrials 1989.000000 Industrials Transportation NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1994
3736 CMPP 2011 PT AirAsia Indonesia Tbk (IDX:CMPP) Industrials 1989.000000 Industrials Transportation NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1994
3737 CMPP 2010 PT AirAsia Indonesia Tbk (IDX:CMPP) Industrials 1989.000000 Industrials Transportation NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1994
3738 CMPP 2009 PT AirAsia Indonesia Tbk (IDX:CMPP) Industrials 1989.000000 Industrials Transportation NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1994
3739 CMPP 2008 PT AirAsia Indonesia Tbk (IDX:CMPP) Industrials 1989.000000 Industrials Transportation NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1994
3859 CPRI 2024 PT Capri Nusa Satu Properti Tbk (IDX:CPRI) Real Estate 2011.000000 Real Estate Real Estate Management and Development NaN NaN 0.720000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2433379958.000000 121668.997900 50.000000 NaN NaN NaN 2019
3860 CPRI 2023 PT Capri Nusa Satu Properti Tbk (IDX:CPRI) Real Estate 2011.000000 Real Estate Real Estate Management and Development NaN NaN 0.720000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2433379958.000000 121668.997900 50.000000 NaN NaN NaN 2019
3861 CPRI 2022 PT Capri Nusa Satu Properti Tbk (IDX:CPRI) Real Estate 2011.000000 Real Estate Real Estate Management and Development NaN NaN 0.720000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2433379958.000000 121668.997900 50.000000 NaN NaN NaN 2019
4182 DEAL 2024 PT Dewata Freightinternational Tbk (IDX:DEAL) Industrials 1995.000000 Industrials Transportation NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1146170959.000000 6877.025754 6.000000 NaN NaN NaN 2018
4183 DEAL 2023 PT Dewata Freightinternational Tbk (IDX:DEAL) Industrials 1995.000000 Industrials Transportation NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1146170959.000000 10315.538631 9.000000 NaN NaN NaN 2018
5134 ETWA 2024 PT Eterindo Wahanatama Tbk (IDX:ETWA) Energy and Utilities 1992.000000 Energy Energy 79.260000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 4668671400.000000 326806.998000 70.000000 NaN NaN NaN 1997
5135 ETWA 2023 PT Eterindo Wahanatama Tbk (IDX:ETWA) Energy and Utilities 1992.000000 Energy Energy 79.260000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 4668671400.000001 452861.125800 97.000000 NaN NaN NaN 1997
5491 GAMA 2024 PT Aksara Global Development Tbk (IDX:GAMA) Real Estate 2003.000000 Real Estate Real Estate Management and Development NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 10011027656.000000 180198.497808 18.000000 NaN NaN NaN 2012
5492 GAMA 2023 PT Aksara Global Development Tbk (IDX:GAMA) Real Estate 2003.000000 Real Estate Real Estate Management and Development NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 10011027656.000000 180198.497808 18.000000 NaN NaN NaN 2012
5493 GAMA 2022 PT Aksara Global Development Tbk (IDX:GAMA) Real Estate 2003.000000 Real Estate Real Estate Management and Development NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 10011027656.000000 500551.382800 50.000000 NaN NaN NaN 2012
5570 GEMS 2013 PT Golden Energy Mines Tbk (IDX:GEMS) Energy and Utilities 1997.000000 Energy Energy 51.000000 0.020000 0.000000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2011
5571 GEMS 2012 PT Golden Energy Mines Tbk (IDX:GEMS) Energy and Utilities 1997.000000 Energy Energy 51.000000 0.020000 0.000000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2011
6273 HKMU 2024 PT HK Metals Utama Tbk (IDX:HKMU) Industrials 1995.000000 Industrials Capital Goods NaN NaN 0.100000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 3221750000.000000 161087.500000 50.000000 NaN NaN NaN 2018
6274 HKMU 2023 PT HK Metals Utama Tbk (IDX:HKMU) Industrials 1995.000000 Industrials Capital Goods NaN NaN 0.100000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 3221750000.000000 161087.500000 50.000000 NaN NaN NaN 2018
6544 ICON 2008 PT Island Concepts Indonesia Tbk (IDX:ICON) Industrials 2001.000000 Industrials Commercial and Professional Services NaN 5.020000 36.140000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2005
7191 IPPE 2024 PT Indo Pureco Pratama Tbk (IDX:IPPE) Consumer 2019.000000 Consumer Staples Food, Beverage and Tobacco NaN NaN 5.320000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 4600000000.000001 64400.000000 14.000000 NaN NaN NaN 2021
7684 KAYU 2024 PT Darmi Bersaudara Tbk (IDX:KAYU) Materials 1998.000000 Materials Materials 54.210000 NaN 0.050000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 665000000.000000 11970.000000 18.000000 NaN NaN NaN 2019
9571 MKNT 2024 PT Mitra Komunikasi Nusantara Tbk (IDX:MKNT) Technology, Media & Telecommunications 2008.000000 Information Technology Technology Hardware and Equipment NaN 5.010000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 5500000000.000000 5500.000000 1.000000 NaN NaN NaN 2015
9572 MKNT 2023 PT Mitra Komunikasi Nusantara Tbk (IDX:MKNT) Technology, Media & Telecommunications 2008.000000 Information Technology Technology Hardware and Equipment NaN 5.010000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 5500000000.000000 11000.000000 2.000000 NaN NaN NaN 2015
10149 MYOH 2024 PT Samindo Resources Tbk (IDX:MYOH) Energy and Utilities 2000.000000 Energy Energy 59.030000 10.570000 14.180000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2206312500.000000 3508036.875000 1590.000000 NaN NaN NaN 2000
11288 PMMP 2024 PT Panca Mitra Multiperdana Tbk (IDX:PMMP) Consumer 1997.000000 Consumer Staples Food, Beverage and Tobacco 51.000000 NaN 62.450000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2588300000.000000 194122.500000 75.000000 NaN NaN NaN 2020
11458 POLL 2024 PT Pollux Properties Indonesia Tbk (IDX:POLL) Real Estate 2014.000000 Real Estate Real Estate Management and Development 84.980000 6.010000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 8318823600.000000 931708.243200 112.000000 NaN NaN NaN 2018
12597 SBAT 2024 PT Sejahtera Bintang Abadi Textile Tbk (IDX:SBAT) Consumer 2003.000000 Consumer Discretionary Consumer Durables and Apparel NaN NaN 34.480000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 4752982978.000000 4752.982978 1.000000 NaN NaN NaN 2020
12598 SBAT 2023 PT Sejahtera Bintang Abadi Textile Tbk (IDX:SBAT) Consumer 2003.000000 Consumer Discretionary Consumer Durables and Apparel NaN NaN 34.480000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 4752982978.000000 42776.846802 9.000000 NaN NaN NaN 2020
12728 SDRA 2012 PT Bank Woori Saudara Indonesia 1906 Tbk (IDX:... Financials 1906.000000 Financials Banks 90.750000 NaN 0.020000 4573918.000000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2006
12729 SDRA 2011 PT Bank Woori Saudara Indonesia 1906 Tbk (IDX:... Financials 1906.000000 Financials Banks 90.750000 NaN 0.020000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2006
12730 SDRA 2010 PT Bank Woori Saudara Indonesia 1906 Tbk (IDX:... Financials 1906.000000 Financials Banks 90.750000 NaN 0.020000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2006
12731 SDRA 2009 PT Bank Woori Saudara Indonesia 1906 Tbk (IDX:... Financials 1906.000000 Financials Banks 90.750000 NaN 0.020000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2006
12732 SDRA 2008 PT Bank Woori Saudara Indonesia 1906 Tbk (IDX:... Financials 1906.000000 Financials Banks 90.750000 NaN 0.020000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2006
13474 SQMI 2014 PT Wilton Makmur indonesia Tbk. (IDX:SQMI) Energy and Utilities 2000.000000 Energy Energy 75.620000 41.730000 20.320000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 301200000.000000 448788.000000 1490.000000 NaN NaN NaN 2004
13736 SWAT 2024 PT Sriwahana Adityakarta Tbk (IDX:SWAT) Materials 1990.000000 Materials Materials 77.220000 NaN 11.800000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 3019200000.000000 75480.000000 25.000000 NaN NaN NaN 2018
13991 TECH 2024 PT Indosterling Technomedia TBK (IDX:TECH) Technology, Media & Telecommunications 2011.000000 Information Technology Software and Services 79.950000 0.050000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1256300000.000000 62815.000000 50.000000 NaN NaN NaN 2020
13992 TECH 2023 PT Indosterling Technomedia TBK (IDX:TECH) Technology, Media & Telecommunications 2011.000000 Information Technology Software and Services 79.950000 0.050000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1256300000.000000 62815.000000 50.000000 NaN NaN NaN 2020
14314 TOPS 2024 PT Totalindo Eka Persada Tbk (IDX:TOPS) Industrials 1996.000000 Industrials Capital Goods 58.750000 NaN 5.800000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 33330000000.000000 33330.000000 1.000000 NaN NaN NaN 2017
14399 TOYS 2024 PT Sunindo Adipersada Tbk (IDX:TOYS) Consumer 1991.000000 Consumer Discretionary Consumer Durables and Apparel NaN NaN 39.900000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 1435000712.000000 11480.005696 8.000000 NaN NaN NaN 2020
14431 TPIA 2009 PT Chandra Asri Pacific Tbk (IDX:TPIA) Materials 1984.000000 Materials Materials NaN 0.600000 5.200000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2008
15640 ZBRA 2024 PT Dosni Roha Indonesia Tbk (IDX:ZBRA) Health Care 1987.000000 Health Care Health Care Equipment and Services 63.210000 30.770000 2.270000 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2510706263.000000 135578.138202 54.000000 NaN NaN NaN 1991

Null pada Kolom Penentu Financial Distress

Selain status masuknya perusahaan ke Papan Pemantauan Khusus, financial distress juga ditentukan dengan Total Ekuitas dan Laba Bersih. Cek apakah ada baris yang memiliki null pada kedua kolom tadi.

print('Baris dengan Net Income to Company atau Total Equity null :\n')
df_findat_final[(df_findat_final['Net Income to Company (Rp.M)'].isna()) | (df_findat_final['Total Equity (Rp.M)'].isna())][['Entity Name','Year', 'Net Income to Company (Rp.M)', 'Total Equity (Rp.M)']]
Baris dengan Net Income to Company atau Total Equity null :
variable Entity Name Year Net Income to Company (Rp.M) Total Equity (Rp.M)
527 PT Tri Banyan Tirta Tbk (IDX:ALTO) 2024 NaN NaN
1904 PT Berkah Beton Sadaya Tbk (IDX:BEBS) 2024 NaN NaN
2108 PT Binakarya Jaya Abadi Tbk (IDX:BIKA) 2024 NaN NaN
2703 PT. Borneo Olah Sarana Sukses Tbk (IDX:BOSS) 2024 NaN NaN
2704 PT. Borneo Olah Sarana Sukses Tbk (IDX:BOSS) 2023 NaN NaN
3002 PT Bumi Teknokultura Unggul Tbk (IDX:BTEK) 2014 NaN NaN
3003 PT Bumi Teknokultura Unggul Tbk (IDX:BTEK) 2013 NaN NaN
3004 PT Bumi Teknokultura Unggul Tbk (IDX:BTEK) 2012 NaN NaN
3005 PT Bumi Teknokultura Unggul Tbk (IDX:BTEK) 2011 NaN NaN
3006 PT Bumi Teknokultura Unggul Tbk (IDX:BTEK) 2010 NaN NaN
3007 PT Bumi Teknokultura Unggul Tbk (IDX:BTEK) 2009 NaN NaN
3008 PT Bumi Teknokultura Unggul Tbk (IDX:BTEK) 2008 NaN NaN
3208 PT Eagle High Plantations Tbk (IDX:BWPT) 2012 NaN NaN
3209 PT Eagle High Plantations Tbk (IDX:BWPT) 2011 NaN NaN
3210 PT Eagle High Plantations Tbk (IDX:BWPT) 2010 NaN NaN
3732 PT AirAsia Indonesia Tbk (IDX:CMPP) 2015 NaN NaN
3733 PT AirAsia Indonesia Tbk (IDX:CMPP) 2014 NaN NaN
3734 PT AirAsia Indonesia Tbk (IDX:CMPP) 2013 NaN NaN
3735 PT AirAsia Indonesia Tbk (IDX:CMPP) 2012 NaN NaN
3736 PT AirAsia Indonesia Tbk (IDX:CMPP) 2011 NaN NaN
3737 PT AirAsia Indonesia Tbk (IDX:CMPP) 2010 NaN NaN
3738 PT AirAsia Indonesia Tbk (IDX:CMPP) 2009 NaN NaN
3739 PT AirAsia Indonesia Tbk (IDX:CMPP) 2008 NaN NaN
3859 PT Capri Nusa Satu Properti Tbk (IDX:CPRI) 2024 NaN NaN
3860 PT Capri Nusa Satu Properti Tbk (IDX:CPRI) 2023 NaN NaN
3861 PT Capri Nusa Satu Properti Tbk (IDX:CPRI) 2022 NaN NaN
4182 PT Dewata Freightinternational Tbk (IDX:DEAL) 2024 NaN NaN
4183 PT Dewata Freightinternational Tbk (IDX:DEAL) 2023 NaN NaN
5134 PT Eterindo Wahanatama Tbk (IDX:ETWA) 2024 NaN NaN
5135 PT Eterindo Wahanatama Tbk (IDX:ETWA) 2023 NaN NaN
5491 PT Aksara Global Development Tbk (IDX:GAMA) 2024 NaN NaN
5492 PT Aksara Global Development Tbk (IDX:GAMA) 2023 NaN NaN
5493 PT Aksara Global Development Tbk (IDX:GAMA) 2022 NaN NaN
5570 PT Golden Energy Mines Tbk (IDX:GEMS) 2013 NaN NaN
5571 PT Golden Energy Mines Tbk (IDX:GEMS) 2012 NaN NaN
6273 PT HK Metals Utama Tbk (IDX:HKMU) 2024 NaN NaN
6274 PT HK Metals Utama Tbk (IDX:HKMU) 2023 NaN NaN
6544 PT Island Concepts Indonesia Tbk (IDX:ICON) 2008 NaN NaN
7191 PT Indo Pureco Pratama Tbk (IDX:IPPE) 2024 NaN NaN
7684 PT Darmi Bersaudara Tbk (IDX:KAYU) 2024 NaN NaN
9571 PT Mitra Komunikasi Nusantara Tbk (IDX:MKNT) 2024 NaN NaN
9572 PT Mitra Komunikasi Nusantara Tbk (IDX:MKNT) 2023 NaN NaN
10149 PT Samindo Resources Tbk (IDX:MYOH) 2024 NaN NaN
11288 PT Panca Mitra Multiperdana Tbk (IDX:PMMP) 2024 NaN NaN
11458 PT Pollux Properties Indonesia Tbk (IDX:POLL) 2024 NaN NaN
12597 PT Sejahtera Bintang Abadi Textile Tbk (IDX:SBAT) 2024 NaN NaN
12598 PT Sejahtera Bintang Abadi Textile Tbk (IDX:SBAT) 2023 NaN NaN
12728 PT Bank Woori Saudara Indonesia 1906 Tbk (IDX:... 2012 NaN NaN
12729 PT Bank Woori Saudara Indonesia 1906 Tbk (IDX:... 2011 NaN NaN
12730 PT Bank Woori Saudara Indonesia 1906 Tbk (IDX:... 2010 NaN NaN
12731 PT Bank Woori Saudara Indonesia 1906 Tbk (IDX:... 2009 NaN NaN
12732 PT Bank Woori Saudara Indonesia 1906 Tbk (IDX:... 2008 NaN NaN
13474 PT Wilton Makmur indonesia Tbk. (IDX:SQMI) 2014 NaN NaN
13736 PT Sriwahana Adityakarta Tbk (IDX:SWAT) 2024 NaN NaN
13991 PT Indosterling Technomedia TBK (IDX:TECH) 2024 NaN NaN
13992 PT Indosterling Technomedia TBK (IDX:TECH) 2023 NaN NaN
14314 PT Totalindo Eka Persada Tbk (IDX:TOPS) 2024 NaN NaN
14399 PT Sunindo Adipersada Tbk (IDX:TOYS) 2024 NaN NaN
14431 PT Chandra Asri Pacific Tbk (IDX:TPIA) 2009 NaN NaN
15640 PT Dosni Roha Indonesia Tbk (IDX:ZBRA) 2024 NaN NaN
print('Keseluruhan data dari perusahaan tersebut :')
perusahaan_yang_laba_atau_ekuitas_null = df_findat_final[(df_findat_final['Net Income to Company (Rp.M)'].isna()) | (df_findat_final['Total Equity (Rp.M)'].isna())]['Entity Name'].unique().tolist()
df_findat_final[df_findat_final['Entity Name'].isin(perusahaan_yang_laba_atau_ekuitas_null)][['Entity Name','Year', 'Net Income to Company (Rp.M)', 'Total Equity (Rp.M)']]
Keseluruhan data dari perusahaan tersebut :
variable Entity Name Year Net Income to Company (Rp.M) Total Equity (Rp.M)
527 PT Tri Banyan Tirta Tbk (IDX:ALTO) 2024 NaN NaN
528 PT Tri Banyan Tirta Tbk (IDX:ALTO) 2023 -25917.766000 323765.890000
529 PT Tri Banyan Tirta Tbk (IDX:ALTO) 2022 -16129.027000 348916.160000
530 PT Tri Banyan Tirta Tbk (IDX:ALTO) 2021 -8932.198000 363835.661000
531 PT Tri Banyan Tirta Tbk (IDX:ALTO) 2020 -10506.939000 372883.080000
... ... ... ... ...
15652 PT Dosni Roha Indonesia Tbk (IDX:ZBRA) 2012 -8699.679000 9807.578000
15653 PT Dosni Roha Indonesia Tbk (IDX:ZBRA) 2011 -9334.155000 18507.257000
15654 PT Dosni Roha Indonesia Tbk (IDX:ZBRA) 2010 -9424.290000 27841.411000
15655 PT Dosni Roha Indonesia Tbk (IDX:ZBRA) 2009 -7642.007000 37729.599000
15656 PT Dosni Roha Indonesia Tbk (IDX:ZBRA) 2008 -7037.408000 45371.606000

291 rows × 4 columns

Untuk konsistensi data dan pelabelan target prediksi, baris yang null pada Total Ekuitas ATAU Net Income perlu dihapus.

df_findat_final = df_findat_final.dropna(subset=['Net Income to Company (Rp.M)', 'Total Equity (Rp.M)'], how='any')

Null Total Asset, Ekuitas, atau Liabilitas

df_findat_final[
    (df_findat_final['Total Assets (Rp.M)'].isna()) |
    (df_findat_final['Total Equity (Rp.M)'].isna()) |
    (df_findat_final['Total Liabilities (Rp.M)'].isna())]
variable ticker Year Entity Name 1st Level Primary Industry Year Established Sector Industry Group Parent Percent Owned (%) Percent Owned - All Institutions (%) Percent Owned - Insiders (%) Total Liabilities (Rp.M) Total Assets (Rp.M) Current Ratio (x) Quick Ratio (x) Working Capital (Rp.M) Total Current Assets (Rp.M) Total Current Liabilities (Rp.M) Inventory (Rp.M) Prepaid Exp. (Rp.M) Long-term Debt (Rp.M) Short-term Borrowings (Rp.M) Current Portion of LT Debt & Leases (Rp.M) Total Debt (Rp.M) Total Equity (Rp.M) Net Property, Plant & Equipment (Rp.M) Cost Of Goods Sold (Rp.M) Total Revenue (Rp.M) Operating Income (Rp.M) EBITDA (Rp.M) EBIT (Rp.M) Net Income to Company (Rp.M) Cash from Ops. (Rp.M) Net Change in Cash (Rp.M) ECS Total Common Shares Outstanding (actual) Market Capitalization (Rp.M) Year Close Stock Price (Rp.) Total Capital (Rp.M) Cash & Short-term Investments (Rp.M) Net Intangibles (Rp.M) IPO Year

Null Total Lembar Saham

print('Baris data yang total lembar sahamnya null:')
df_findat_final[df_findat_final['ECS Total Common Shares Outstanding (actual)'].isna()][['Entity Name','Year', 'ECS Total Common Shares Outstanding (actual)']]
Baris data yang total lembar sahamnya null:
variable Entity Name Year ECS Total Common Shares Outstanding (actual)
12727 PT Bank Woori Saudara Indonesia 1906 Tbk (IDX:... 2013 NaN
print("Perusahaan yang memiliki baris total lembar saham null:")
tickers_with_null_total_shares = df_findat_final[df_findat_final['ECS Total Common Shares Outstanding (actual)'].isna()]['Entity Name'].unique().tolist()
tickers_with_null_total_shares
Perusahaan yang memiliki baris total lembar saham null:
['PT Bank Woori Saudara Indonesia 1906 Tbk (IDX:SDRA)']
print('Cek apakah perusahaan tsb ada baris lain yang tidak null pada total lembar sahamnya :')
df_findat_final[df_findat_final['Entity Name'].isin(tickers_with_null_total_shares)][['Entity Name','Year', 'ECS Total Common Shares Outstanding (actual)']]
Cek apakah perusahaan tsb ada baris lain yang tidak null pada total lembar sahamnya :
variable Entity Name Year ECS Total Common Shares Outstanding (actual)
12716 PT Bank Woori Saudara Indonesia 1906 Tbk (IDX:... 2024 14692189889.000000
12717 PT Bank Woori Saudara Indonesia 1906 Tbk (IDX:... 2023 8568234364.000000
12718 PT Bank Woori Saudara Indonesia 1906 Tbk (IDX:... 2022 8568234364.000000
12719 PT Bank Woori Saudara Indonesia 1906 Tbk (IDX:... 2021 8568234364.000000
12720 PT Bank Woori Saudara Indonesia 1906 Tbk (IDX:... 2020 6580926254.000000
12721 PT Bank Woori Saudara Indonesia 1906 Tbk (IDX:... 2019 6580926254.000000
12722 PT Bank Woori Saudara Indonesia 1906 Tbk (IDX:... 2018 6580926254.000000
12723 PT Bank Woori Saudara Indonesia 1906 Tbk (IDX:... 2017 6580926254.000000
12724 PT Bank Woori Saudara Indonesia 1906 Tbk (IDX:... 2016 5072356660.000000
12725 PT Bank Woori Saudara Indonesia 1906 Tbk (IDX:... 2015 5072356660.000000
12726 PT Bank Woori Saudara Indonesia 1906 Tbk (IDX:... 2014 5072356660.000000
12727 PT Bank Woori Saudara Indonesia 1906 Tbk (IDX:... 2013 NaN
print('Perusahaan yang punya data lembar saham di baris lain:')
df_findat_final[(df_findat_final['Entity Name'].isin(tickers_with_null_total_shares)) & (df_findat_final['ECS Total Common Shares Outstanding (actual)'].notna())]['Entity Name'].unique().tolist()
Perusahaan yang punya data lembar saham di baris lain:
['PT Bank Woori Saudara Indonesia 1906 Tbk (IDX:SDRA)']

Baris Tidak Layak Pakai

Cek dan hapus baris-baris yang tidak layak pakai, untuk baris yang :
- terlalu banyak null - null pada account keuangan yang krusial - pola null tidak masuk akal sehingga merusak konsistensi data

# TODO

Kesesuaian Tipe Data Kolom

df_findat_final.info()
<class 'pandas.core.frame.DataFrame'>
Index: 8489 entries, 17 to 15693
Data columns (total 40 columns):
 #   Column                                        Non-Null Count  Dtype  
---  ------                                        --------------  -----  
 0   ticker                                        8489 non-null   object 
 1   Year                                          8489 non-null   int64  
 2   Entity Name                                   8489 non-null   object 
 3   1st Level Primary Industry                    8489 non-null   object 
 4   Year Established                              8468 non-null   float64
 5   Sector                                        8489 non-null   object 
 6   Industry Group                                8489 non-null   object 
 7   Parent Percent Owned (%)                      4848 non-null   float64
 8   Percent Owned - All Institutions (%)          4542 non-null   float64
 9   Percent Owned - Insiders (%)                  5938 non-null   float64
 10  Total Liabilities (Rp.M)                      8489 non-null   float64
 11  Total Assets (Rp.M)                           8489 non-null   float64
 12  Current Ratio (x)                             7783 non-null   float64
 13  Quick Ratio (x)                               7785 non-null   float64
 14  Working Capital (Rp.M)                        8489 non-null   float64
 15  Total Current Assets (Rp.M)                   8489 non-null   float64
 16  Total Current Liabilities (Rp.M)              8489 non-null   float64
 17  Inventory (Rp.M)                              7283 non-null   float64
 18  Prepaid Exp. (Rp.M)                           7027 non-null   float64
 19  Long-term Debt (Rp.M)                         5908 non-null   float64
 20  Short-term Borrowings (Rp.M)                  5579 non-null   float64
 21  Current Portion of LT Debt & Leases (Rp.M)    337 non-null    float64
 22  Total Debt (Rp.M)                             8468 non-null   float64
 23  Total Equity (Rp.M)                           8489 non-null   float64
 24  Net Property, Plant & Equipment (Rp.M)        8470 non-null   float64
 25  Cost Of Goods Sold (Rp.M)                     8309 non-null   float64
 26  Total Revenue (Rp.M)                          8458 non-null   float64
 27  Operating Income (Rp.M)                       8488 non-null   float64
 28  EBITDA (Rp.M)                                 7383 non-null   float64
 29  EBIT (Rp.M)                                   7390 non-null   float64
 30  Net Income to Company (Rp.M)                  8489 non-null   float64
 31  Cash from Ops. (Rp.M)                         8487 non-null   float64
 32  Net Change in Cash (Rp.M)                     8487 non-null   float64
 33  ECS Total Common Shares Outstanding (actual)  8488 non-null   float64
 34  Market Capitalization (Rp.M)                  8464 non-null   float64
 35  Year Close Stock Price (Rp.)                  8464 non-null   float64
 36  Total Capital (Rp.M)                          8468 non-null   float64
 37  Cash & Short-term Investments (Rp.M)          8489 non-null   float64
 38  Net Intangibles (Rp.M)                        1763 non-null   float64
 39  IPO Year                                      8489 non-null   Int64  
dtypes: Int64(1), float64(33), int64(1), object(5)
memory usage: 2.7+ MB
df_findat_final['Year Established'].head(3)
Year Established
17 1988.000000
18 1988.000000
19 1988.000000

Kolom ‘Year Established’ harusnya integer, bukan float.

import pandas.testing as tm

original_col = df_findat_final['Year Established'].copy()
converted_col = df_findat_final['Year Established'].astype('Int64')

try:
    # convert balik 'converted_col' ke float hanya untuk perbandingan
    # check_dtype=False: abaikan beda tipe (float vs int), fokus ke nilainya
    tm.assert_series_equal(original_col, converted_col.astype(float), check_dtype=False)
    print("SUKSES: Semua nilai sama persis. Konversi aman.")
except AssertionError as e:
    print("GAGAL: Ada nilai yang berubah (mungkin karena pembulatan desimal).")
    print(e)
SUKSES: Semua nilai sama persis. Konversi aman.
# Cek apakah ada nilai yang memiliki koma/desimal (selain NaN)
# dropna() digunakan agar NaN tidak dianggap error
ada_desimal = (df_findat_final['Year Established'].dropna() % 1 != 0).any()

if ada_desimal:
    print("HATI-HATI: Ada data yang mengandung desimal. Konversi ke Int64 akan membuang komanya.")
    # Lihat data mana yang punya desimal
    print(df_findat_final[df_findat_final['Year Established'] % 1 != 0])
else:
    print("AMAN: Semua data adalah bilangan bulat atau NaN.")
AMAN: Semua data adalah bilangan bulat atau NaN.
print('CONVERT')
df_findat_final['Year Established'] = df_findat_final['Year Established'].astype('Int64')
print("Cek isi 'Year Established' sekarang:")
df_findat_final['Year Established'].head(3)
CONVERT
Cek isi 'Year Established' sekarang:
Year Established
17 1988
18 1988
19 1988

Sort dan Reset Index

df_findat_final = (
    df_findat_final
    .sort_values(by=["ticker", "Year"], ascending=[True,  True])
    .reset_index(drop=True)
)

Hasil Dataset Finansial

print('Hasil dataset df_findat_final :')
print(df_findat_final.shape)
df_findat_final
Hasil dataset df_findat_final :
(8489, 40)
variable ticker Year Entity Name 1st Level Primary Industry Year Established Sector Industry Group Parent Percent Owned (%) Percent Owned - All Institutions (%) Percent Owned - Insiders (%) Total Liabilities (Rp.M) Total Assets (Rp.M) Current Ratio (x) Quick Ratio (x) Working Capital (Rp.M) Total Current Assets (Rp.M) Total Current Liabilities (Rp.M) Inventory (Rp.M) Prepaid Exp. (Rp.M) Long-term Debt (Rp.M) Short-term Borrowings (Rp.M) Current Portion of LT Debt & Leases (Rp.M) Total Debt (Rp.M) Total Equity (Rp.M) Net Property, Plant & Equipment (Rp.M) Cost Of Goods Sold (Rp.M) Total Revenue (Rp.M) Operating Income (Rp.M) EBITDA (Rp.M) EBIT (Rp.M) Net Income to Company (Rp.M) Cash from Ops. (Rp.M) Net Change in Cash (Rp.M) ECS Total Common Shares Outstanding (actual) Market Capitalization (Rp.M) Year Close Stock Price (Rp.) Total Capital (Rp.M) Cash & Short-term Investments (Rp.M) Net Intangibles (Rp.M) IPO Year
0 AALI 2008 PT Astra Agro Lestari Tbk (IDX:AALI) Consumer 1988 Consumer Staples Food, Beverage and Tobacco 79.680000 2.100000 NaN 1183215.000000 6519791.000000 1.944000 0.878000 959489.000000 1975656.000000 1016167.000000 781363.000000 37429.000000 NaN NaN NaN 0.000000 5336576.000000 4123645.000000 4357818.000000 8161217.000000 3370969.000000 3616720.000000 3370969.000000 2715518.000000 2087429.000000 -145096.000000 1574745000.000000 15432501.000000 9800.000000 5336576.000000 867676.000000 NaN 1997
1 AALI 2009 PT Astra Agro Lestari Tbk (IDX:AALI) Consumer 1988 Consumer Staples Food, Beverage and Tobacco 79.680000 2.100000 NaN 1144783.000000 7571399.000000 1.826000 1.007000 775450.000000 1714426.000000 938976.000000 610031.000000 36849.000000 NaN NaN NaN 0.000000 6426616.000000 5242447.000000 4322498.000000 7424283.000000 2603280.000000 2898342.000000 2603280.000000 1729648.000000 1984894.000000 -79127.000000 1574745000.000000 35825448.750000 22750.000000 6426616.000000 788549.000000 NaN 1997
2 AALI 2010 PT Astra Agro Lestari Tbk (IDX:AALI) Consumer 1988 Consumer Staples Food, Beverage and Tobacco 79.680000 2.100000 NaN 1334542.000000 8791799.000000 1.932000 1.262000 989325.000000 2051177.000000 1061852.000000 624694.000000 22315.000000 NaN NaN NaN 0.000000 7457257.000000 6103150.000000 5234372.000000 8843721.000000 2992793.000000 3335987.000000 2992793.000000 2103652.000000 2946657.000000 452232.000000 1574745000.000000 41258319.000000 26200.000000 7457257.000000 1240781.000000 NaN 1997
3 AALI 2011 PT Astra Agro Lestari Tbk (IDX:AALI) Consumer 1988 Consumer Staples Food, Beverage and Tobacco 79.680000 2.100000 NaN 1778337.000000 10204495.000000 1.265000 0.582000 389456.000000 1857025.000000 1467569.000000 769903.000000 NaN NaN NaN NaN 0.000000 8426158.000000 7702571.000000 6837674.000000 10772582.000000 3195661.000000 3572651.000000 3195661.000000 2498565.000000 3162475.000000 -402591.000000 1574745000.000000 34171966.500000 21700.000000 8426158.000000 838190.000000 NaN 1997
4 AALI 2012 PT Astra Agro Lestari Tbk (IDX:AALI) Consumer 1988 Consumer Staples Food, Beverage and Tobacco 79.680000 2.100000 NaN 4701077.000000 12419820.000000 0.685000 0.107000 -820145.000000 1780395.000000 2600540.000000 1249050.000000 NaN NaN 971950.000000 NaN 971950.000000 9365411.000000 9894266.000000 7206837.000000 11564319.000000 3453729.000000 3973578.000000 3453729.000000 2520266.000000 2609511.000000 -610421.000000 1574745000.000000 31022476.500000 19700.000000 10337361.000000 227769.000000 NaN 1997
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
8484 ZONE 2023 PT Mega Perintis Tbk (IDX:ZONE) Consumer 2005 Consumer Discretionary Consumer Discretionary Distribution and Retail NaN NaN 62.030000 376866.895000 752956.580000 1.787000 0.183000 187443.551000 425743.875000 238300.324000 370381.888000 8229.119000 45537.359000 85403.842000 NaN 230078.796000 376089.685000 254442.974000 327373.203000 735452.174000 80467.569000 109639.609000 80467.569000 46972.766000 101276.087000 -292.586000 870171478.000000 965890.340580 1110.000000 606168.481000 4617.740000 36266.170000 2018
8485 ZONE 2024 PT Mega Perintis Tbk (IDX:ZONE) Consumer 2005 Consumer Discretionary Consumer Discretionary Distribution and Retail NaN NaN 62.030000 378069.858000 748744.072000 1.750000 0.178000 175199.380000 408802.739000 233603.359000 356567.003000 9325.106000 37101.093000 111850.141000 NaN 262139.509000 370674.214000 262571.197000 347862.483000 708360.249000 33764.860000 68145.652000 33764.860000 7044.347000 92620.371000 3123.094000 870171478.000000 717891.469350 825.000000 632813.723000 7740.834000 36707.806000 2018
8486 ZYRX 2022 PT Zyrexindo Mandiri Buana Tbk (IDX:ZYRX) Technology, Media & Telecommunications 1996 Information Technology Technology Hardware and Equipment NaN NaN 75.160000 438940.489000 707229.819000 1.442000 0.668000 190967.877000 623252.109000 432284.232000 321857.019000 32.094000 NaN 154814.692000 NaN 154972.192000 268289.329000 62069.361000 626487.712000 770370.215000 124050.223000 126197.456000 124050.223000 78627.417000 108226.350000 205303.596000 1333333837.000000 426666.704320 320.000000 423261.521000 206376.347000 NaN 2021
8487 ZYRX 2023 PT Zyrexindo Mandiri Buana Tbk (IDX:ZYRX) Technology, Media & Telecommunications 1996 Information Technology Technology Hardware and Equipment NaN NaN 75.160000 201592.951000 491041.729000 2.073000 0.401000 208245.855000 402244.527000 193998.671000 320652.316000 1146.130000 136.413000 123103.971000 NaN 123328.362000 289448.779000 68457.678000 198332.059000 289633.302000 75643.212000 77211.203000 75643.212000 32952.892000 -128496.218000 -193672.184000 1333334556.000000 221333.536296 166.000000 412777.140000 12704.163000 149.572000 2021
8488 ZYRX 2024 PT Zyrexindo Mandiri Buana Tbk (IDX:ZYRX) Technology, Media & Telecommunications 1996 Information Technology Technology Hardware and Equipment NaN NaN 75.160000 92393.711000 392444.599000 3.402000 0.775000 201817.814000 285854.627000 84036.813000 219621.479000 1021.593000 41.627000 46780.579000 NaN 47050.482000 300050.888000 92660.098000 296495.058000 364238.674000 37276.725000 38767.823000 37276.725000 15171.892000 113472.348000 -6431.849000 1333334556.000000 177333.495948 133.000000 347101.370000 6272.314000 388.017000 2021

8489 rows × 40 columns

df_findat_final.isna().sum()
0
variable
ticker 0
Year 0
Entity Name 0
1st Level Primary Industry 0
Year Established 21
Sector 0
Industry Group 0
Parent Percent Owned (%) 3641
Percent Owned - All Institutions (%) 3947
Percent Owned - Insiders (%) 2551
Total Liabilities (Rp.M) 0
Total Assets (Rp.M) 0
Current Ratio (x) 706
Quick Ratio (x) 704
Working Capital (Rp.M) 0
Total Current Assets (Rp.M) 0
Total Current Liabilities (Rp.M) 0
Inventory (Rp.M) 1206
Prepaid Exp. (Rp.M) 1462
Long-term Debt (Rp.M) 2581
Short-term Borrowings (Rp.M) 2910
Current Portion of LT Debt & Leases (Rp.M) 8152
Total Debt (Rp.M) 21
Total Equity (Rp.M) 0
Net Property, Plant & Equipment (Rp.M) 19
Cost Of Goods Sold (Rp.M) 180
Total Revenue (Rp.M) 31
Operating Income (Rp.M) 1
EBITDA (Rp.M) 1106
EBIT (Rp.M) 1099
Net Income to Company (Rp.M) 0
Cash from Ops. (Rp.M) 2
Net Change in Cash (Rp.M) 2
ECS Total Common Shares Outstanding (actual) 1
Market Capitalization (Rp.M) 25
Year Close Stock Price (Rp.) 25
Total Capital (Rp.M) 21
Cash & Short-term Investments (Rp.M) 0
Net Intangibles (Rp.M) 6726
IPO Year 0

Pelabelan Target Prediksi Financial Distress

Fungsi Integrasi

def distress_target_labeling_and_adjustment(
    df_findat_final: pd.DataFrame,
    df_emiten_idx: pd.DataFrame,
    range_years: int,
    distress_type: str,
    labeling_logic: str,
    col_ticker: str = "ticker",
    col_year: str = "Year",
    col_equity: str = "Total Equity (Rp.M)",
    col_net_income: str = "Net Income to Company (Rp.M)",
    col_kode: str = "Kode",
    col_tanggal_masuk: str = "Tanggal Masuk",
    col_distress_ppk: str = "Distress_PPK",
    col_distress_year: str = "tahun_distress",
    col_target: str = "target_distress",
    ppk_year_min: int = 2021,
    ppk_year_max: int = 2025,
    verbose: bool = True,
) -> pd.DataFrame:
    """
    Membuat label target prediksi distress (0/1/NA) + tahun distress pertama (tahun_distress)
    berdasarkan salah satu indikator distress: 'ppk', 'neg_equity', 'consecutive_loss'.

    Note:
    - Baris yang tidak bisa ditentukan targetnya TIDAK dihapus; target_distress cukup di-NA-kan.
    - Aturan "no recovery" diterapkan dengan meng-NA-kan target pada tahun-tahun tertentu setelah distress.
    - Untuk neg_equity: tahun dianggap ambigu bila row missing atau equity null.
    - Untuk consecutive_loss: distress(y) hanya observable bila NI(y) dan NI(y-1) ada & tidak null & year berurutan.
    - Untuk PPK: gunakan Distress_PPK == 'Yes', coverage diasumsikan ppk_year_min hingga ppk_year_max.

    Parameters
    ----------
    range_years : int > 0
    labeling_logic : 'within' atau 'in_exactly'
      - within: target=1 bila distress terjadi pada t+1..t+range
      - in_exactly: target=1 bila distress terjadi tepat pada t+range

    Returns
    -------
    df_out : pd.DataFrame
      Copy dari df_findat_final + kolom tahun_distress dan target_distress (nullable Int64).
    """

    # Range harus >0
    if not isinstance(range_years, int) or range_years <= 0:
        raise ValueError("range_years harus integer > 0")

    distress_type = str(distress_type).strip().lower()
    labeling_logic = str(labeling_logic).strip().lower()

    if distress_type not in {"ppk", "neg_equity", "consecutive_loss"}:
        raise ValueError("distress_type harus salah satu dari: 'ppk', 'neg_equity', 'consecutive_loss'")
    if labeling_logic not in {"within", "in_exactly"}:
        raise ValueError("labeling_logic harus 'within' atau 'in_exactly'")

    # copy dari df_findat_final
    df = df_findat_final.copy()

    # Cek ketersediaan kolom
    required_cols = {col_ticker, col_year}
    missing_cols = [c for c in required_cols if c not in df.columns]
    if missing_cols:
        raise KeyError(f"Kolom wajib tidak ditemukan di df_findat_final: {missing_cols}")

    # Pastikan Year numeric int
    df[col_year] = pd.to_numeric(df[col_year], errors="coerce").astype("Int64")
    if df[col_year].isna().any():
        raise ValueError("Ada nilai Year yang tidak bisa dikonversi ke integer.")

    if verbose:
        print("--- MEMPROSES DATA DENGAN KONFIGURASI BERIKUT ---")
        print(f"Tipe distress       : {distress_type}")
        print(f"Range tahun prediksi: {range_years}")
        print(f"Logika pelabelan    : {labeling_logic}")
        print(f"Shape awal          : {df.shape}")

    # ======================================================================
    # A) Hitung tahun_distress per ticker (distress_year_map: ticker -> distress year)
    # ======================================================================
    distress_year_map = pd.Series(dtype="Int64")

    if distress_type == "ppk":
        # Cek ketersediaan kolom wajid di df_emiten_idx
        for c in [col_kode, col_distress_ppk, col_tanggal_masuk]:
            if c not in df_emiten_idx.columns:
                raise KeyError(f"Kolom '{c}' tidak ditemukan di df_emiten_idx")

        emiten = df_emiten_idx.copy()
        emiten[col_tanggal_masuk] = pd.to_datetime(emiten[col_tanggal_masuk], errors="coerce")

        # Cari yang Distress_PPK bernilai YES dan ada tanggal masuknya
        emiten_distress = emiten.loc[emiten[col_distress_ppk].astype(str).str.upper().eq("YES") & emiten[col_tanggal_masuk].notna(), [col_kode, col_tanggal_masuk]]

        # Ada distress
        if not emiten_distress.empty:
            emiten_distress = emiten_distress.assign(_year=emiten_distress[col_tanggal_masuk].dt.year.astype("Int64"))
            distress_year_map = emiten_distress.groupby(col_kode)["_year"].min().astype("Int64")
        # Tidak ada distress
        else:
            distress_year_map = pd.Series(dtype="Int64")

    elif distress_type == "neg_equity":
        # Cek ketersediaan kolom ekuitas di df_findat_final
        if col_equity not in df.columns:
            raise KeyError(f"Kolom '{col_equity}' tidak ditemukan di df_findat_final")

        # Pastikan numerik dan cari yang negatif tidak null
        equity = pd.to_numeric(df[col_equity], errors="coerce")
        mask_neg = equity.notna() & (equity < 0)

        # distress year = tahun pertama equity negatif yang TEROBSERVASI (non-null)
        if mask_neg.any():
            distress_year_map = (
                df.loc[mask_neg]
                .groupby(col_ticker)[col_year]
                .min()
                .astype("Int64")
            )
        else:
            distress_year_map = pd.Series(dtype="Int64")

    else:  # distress_type == "consecutive_loss"
        # Cek ketersediaan kolom net income di df_findat_final
        if col_net_income not in df.columns:
            raise KeyError(f"Kolom '{col_net_income}' tidak ditemukan di df_findat_final")

        # pastikan numerik
        tmp = df[[col_ticker, col_year, col_net_income]].copy()
        tmp[col_net_income] = pd.to_numeric(tmp[col_net_income], errors="coerce")

        # sort berdasarkan ticker-year agar shift benar
        tmp = tmp.sort_values([col_ticker, col_year])

        # cari nilai prev year dan prev net income untuk tiap ticker-year
        prev_year = tmp.groupby(col_ticker)[col_year].shift(1)
        prev_ni = tmp.groupby(col_ticker)[col_net_income].shift(1)

        # distress di tahun y JIKA DAN HANYA JIKA y dan y-1 ada dan berurutan, NI(y) & NI(y-1) non-null dan negatif
        cond = (
            tmp[col_net_income].notna() & # net income tahun tsb ada
            prev_ni.notna() & # net income tahun sebelumnya ada
            prev_year.notna() & # tahun sebelumnya ada
            ((tmp[col_year] - prev_year) == 1) & # selisih tahun tsb dan tahun sbelumnya 1 (negatif berturut-turut)
            (tmp[col_net_income] < 0) & # net income tahun tsb negatif
            (prev_ni < 0) # net income tahun sebelumnya negatif
        )

        if cond.any(): # ditemukan distress
            distress_year_map = (
                tmp.loc[cond]
                .groupby(col_ticker)[col_year]
                .min()
                .astype("Int64")
            )
        else: # tidak ditemukan distress
            distress_year_map = pd.Series(dtype="Int64")

    # Assign tahun_distress ke semua baris
    df[col_distress_year] = df[col_ticker].map(distress_year_map).astype("Int64")

    # ======================================================================
    # B) Inisialisasi target_distress (0/1) lalu apply 'censoring + ambiguity' dengan NA
    # ======================================================================

    # buat kolom, isi 0 semua dulu
    df[col_target] = pd.Series(0, index=df.index, dtype="Int64")

    t = df[col_year].astype(int) # tahun data
    d = df[col_distress_year]    # tahun distress Int64 (nullable)
    r = range_years              # rentang tahun prediksi

    t_arr = df[col_year].to_numpy(dtype=float)            # e.g. [2019., 2020., ...]
    d_arr = df[col_distress_year].to_numpy(dtype=float)   # e.g. [2023., nan, ...]
    has_d = ~np.isnan(d_arr)

    if labeling_logic == "within":
        mask_one = has_d & (d_arr >= (t_arr + 1)) & (d_arr <= (t_arr + r)) # ada distress, terjadi antara t+1 hingga t+r inklusif
    else:  # labeling_logic == "in_exactly"
        mask_one = has_d & (d_arr == (t_arr + r)) # ada distress, terjadi tepat t+r

    df.loc[mask_one, col_target] = 1 # baris yang memenuhi diisi 1

    # ======================================================================
    # C) "No recovery" censoring: set NA untuk tahun-tahun yang tidak dipakai
    # ======================================================================

    if labeling_logic == "within":
        # NA untuk Year >= tahun_distress
        mask_censor = has_d & (t_arr >= d_arr) # d.notna() & (t >= d.astype(int))
    else: # labeling_logic == "in_exactly"
        # NA untuk Year >= (tahun_distress - r + 1)
        mask_censor = has_d & (t_arr >= (d_arr - r + 1)) # d.notna() & (t >= (d.astype(int) - r + 1))

    df.loc[mask_censor, col_target] = pd.NA

    # ======================================================================
    # D) Ambiguity / observability: hanya mempengaruhi label 0 (kalau tidak pasti => NA)
    #    - within: butuh semua tahun t+1..t+r harus observable
    #    - in_exactly: butuh tahun t+r saja yang harus observable
    # ======================================================================
    # Hanya cek baris yang saat ini masih 0 (bukan 1, bukan NA)
    mask_zero = df[col_target].eq(0)

    if mask_zero.any():
        if distress_type == "ppk":
            # Kalau ticker tidak ada di df_emiten_idx, tidak observable.
            known_tickers = set(df_emiten_idx[col_kode].astype(str).unique()) if col_kode in df_emiten_idx.columns else set()
            ticker_known = df[col_ticker].astype(str).isin(known_tickers)

            if labeling_logic == "within":
                observability_ok = (
                    ticker_known & # ticker harus known
                    ((t + 1) >= ppk_year_min) & # year + 1 harus >= ppk tersedia terkecil
                    ((t + r) <= ppk_year_max) # year + range harus <= ppk tersedia terbesar
                )
            else: # labeling_logic == "in_exactly"
                observability_ok = (
                    ticker_known & # ticker harus known
                    ((t + r) >= ppk_year_min) & # year + range harus >= ppk tersedia terkecil
                    ((t + r) <= ppk_year_max) # year + range harus <= ppk tersedia terbesar
                )

            # pada baris yang kolom target 0 dan tidak observable, isi null
            df.loc[mask_zero & ~observability_ok, col_target] = pd.NA

            # REVISI untuk within, label 1 juga wajib observable (kalau tidak -> NA)
            # bertujuan agar tahun-tahun akhir tidak menumpuk label distress semua, proporsi distress train-test lebih setara
            # karena baris dg tahun-tahun terakhir tidak memiliki r tahun ke depan
            if labeling_logic == "within":
                df.loc[df[col_target].eq(1) & ~observability_ok, col_target] = pd.NA

        elif distress_type == "neg_equity":
            equity = pd.to_numeric(df[col_equity], errors="coerce") # pastikan equity numeric
            df_equity_observable = df.loc[equity.notna(), [col_ticker, col_year]].copy() # ticker-year yang equity tidak null, bisa ditentukan distressnya

            # MultiIndex pasangan (ticker, year) yang OBSERVABLE untuk equity
            index_equity_observable = pd.MultiIndex.from_frame(df_equity_observable.astype({col_year: int}))

            if labeling_logic == "within":
                observability_ok = np.ones(len(df), dtype=bool) # isi 1 semua dulu
                for k in range(1, r + 1): # cek untuk tiap r dari 1 sampai r -> ( tahun target t+1, t+2, ... , t+r )
                    candidate = pd.MultiIndex.from_arrays([df[col_ticker].astype(str), (t + k).astype(int)]) # buat multiindex pair ticker-target(t+k)
                    observability_ok &= candidate.isin(index_equity_observable) # cek apakah ada di observable, jika iya maka boolean diakumulasi ke observability_ok
                observability_ok = pd.Series(observability_ok, index=df.index) # ubah ke series agar indeksnya sama dengan df
            else: # labeling_logic == "in_exactly"
                candidate = pd.MultiIndex.from_arrays([df[col_ticker].astype(str), (t + r).astype(int)]) # hanya cek t+r
                observability_ok = pd.Series(candidate.isin(index_equity_observable), index=df.index) # cek apakah observable

            df.loc[mask_zero & ~observability_ok, col_target] = pd.NA # yang target 0 dan tidak observable, maka diset null

            # REVISI untuk within, label 1 juga wajib observable (kalau tidak -> NA)
            # bertujuan agar tahun-tahun akhir tidak menumpuk label distress semua, proporsi distress train-test lebih setara
            # karena baris dg tahun-tahun terakhir tidak memiliki r tahun ke depan
            if labeling_logic == "within":
                df.loc[df[col_target].eq(1) & ~observability_ok, col_target] = pd.NA

        else:  # distress_type == "consecutive_loss"
            ni = pd.to_numeric(df[col_net_income], errors="coerce") # pastikan net income numeric
            df_ni_observable = df.loc[ni.notna(), [col_ticker, col_year]].copy() # ticker-year yang net income tidak null, bisa ditentukan distressnya

            # MultiIndex pasangan (ticker, year) yang OBSERVABLE untuk NI (row ada & NI non-null)
            index_ni_observable = pd.MultiIndex.from_frame(df_ni_observable.astype({col_year: int}))

            if labeling_logic == "within":
                observability_ok = np.ones(len(df), dtype=bool) # isi 1 semua dulu
                for k in range(1, r + 1): # cek untuk tiap r dari 1 sampai r -> ( tahun target t+1, t+2, ... , t+r )
                    y = (t + k).astype(int) # tahun target t+k
                    y_prev = (t + k - 1).astype(int) # tahun sebelumnya

                    candidate_y = pd.MultiIndex.from_arrays([df[col_ticker].astype(str), y]) # multiindex ticker dan y
                    candidate_prev = pd.MultiIndex.from_arrays([df[col_ticker].astype(str), y_prev]) # multiindex ticker dan y_prev

                    # distress(y) observable hanya jika NI(y) dan NI(y-1) observable
                    observability_ok &= candidate_y.isin(index_ni_observable) & candidate_prev.isin(index_ni_observable) # cek apakah y dan yprev ada di observable, jika iya maka boolean diakumulasi ke observability_ok

                observability_ok = pd.Series(observability_ok, index=df.index) # sesuaikan index
            else: # labeling_logic == "in_exactly" (cek hanya untuk tepat r tahun setelah t)
                y = (t + r).astype(int) # tahun target t+k
                y_prev = (t + r - 1).astype(int) # tahun sebelum target

                candidate_y = pd.MultiIndex.from_arrays([df[col_ticker].astype(str), y]) # multiindex ticker dan y
                candidate_prev = pd.MultiIndex.from_arrays([df[col_ticker].astype(str), y_prev]) # multiindex ticker dan yprev

                observability_ok = pd.Series(candidate_y.isin(index_ni_observable) & candidate_prev.isin(index_ni_observable), index=df.index) # cek apakah y dan yprev ada di observable, masukkan ke observability_ok

            df.loc[mask_zero & ~observability_ok, col_target] = pd.NA # yang targetnya 0 dan observabilty tidak ok, maka null

            # REVISI untuk within, label 1 juga wajib observable (kalau tidak -> NA)
            # bertujuan agar tahun-tahun akhir tidak menumpuk label distress semua, proporsi distress train-test lebih setara
            # karena baris dg tahun-tahun terakhir tidak memiliki r tahun ke depan
            if labeling_logic == "within":
                df.loc[df[col_target].eq(1) & ~observability_ok, col_target] = pd.NA

    # Urutkan Kolom
    first_cols = [col_ticker, col_year, 'Entity Name', col_target, col_distress_year, col_equity, col_net_income]
    df = df[first_cols + [col for col in df.columns if col not in first_cols]]

    if verbose:
        vc = df[col_target].value_counts(dropna=False)
        print("--- RINGKASAN HASIL LABELING ---")
        print(vc)
        print()

    return df
def distress_target_labeling_and_adjustment_all_indicators_at_once(
    df_findat_final: pd.DataFrame,
    df_emiten_idx: pd.DataFrame,
    range_years: int,
    # distress_type: str,
    labeling_logic: str,
    col_ticker: str = "ticker",
    col_year: str = "Year",
    col_equity: str = "Total Equity (Rp.M)",
    col_net_income: str = "Net Income to Company (Rp.M)",
    col_kode: str = "Kode",
    col_tanggal_masuk: str = "Tanggal Masuk",
    col_distress_ppk: str = "Distress_PPK",
    col_distress_year: str = "tahun_distress",
    col_target: str = "target_distress",
    ppk_year_min: int = 2021,
    ppk_year_max: int = 2025,
    verbose: bool = True,
) -> pd.DataFrame:
    """
    Membuat label target prediksi distress (0/1/NA) + tahun distress pertama (tahun_distress)
    berdasarkan salah satu indikator distress: 'ppk', 'neg_equity', 'consecutive_loss'.

    Note:
    - Baris yang tidak bisa ditentukan targetnya TIDAK dihapus; target_distress cukup di-NA-kan.
    - Aturan "no recovery" diterapkan dengan meng-NA-kan target pada tahun-tahun tertentu setelah distress.
    - Untuk neg_equity: tahun dianggap ambigu bila row missing atau equity null.
    - Untuk consecutive_loss: distress(y) hanya observable bila NI(y) dan NI(y-1) ada & tidak null & year berurutan.
    - Untuk PPK: gunakan Distress_PPK == 'Yes', coverage diasumsikan ppk_year_min hingga ppk_year_max.

    Parameters
    ----------
    range_years : int > 0
    labeling_logic : 'within' atau 'in_exactly'
        - within: target=1 bila distress terjadi pada t+1..t+range
        - in_exactly: target=1 bila distress terjadi tepat pada t+range

    Returns
    -------
    df_out : pd.DataFrame
        Copy dari df_findat_final + kolom tahun_distress dan target_distress (nullable Int64).
    """

    # CEK INPUT KONFIGURASI
    # Range harus >0
    if not isinstance(range_years, int) or range_years <= 0:
        raise ValueError("range_years harus integer > 0")

    # distress_type = str(distress_type).strip().lower()
    labeling_logic = str(labeling_logic).strip().lower()

    # if distress_type not in {"ppk", "neg_equity", "consecutive_loss"}:
    #     raise ValueError("distress_type harus salah satu dari: 'ppk', 'neg_equity', 'consecutive_loss'")
    if labeling_logic not in {"within", "in_exactly"}:
        raise ValueError("labeling_logic harus 'within' atau 'in_exactly'")

    # copy dari df_findat_final
    df = df_findat_final.copy()

    # Cek ketersediaan kolom
    required_cols = {col_ticker, col_year}
    missing_cols = [c for c in required_cols if c not in df.columns]
    if missing_cols:
        raise KeyError(f"Kolom wajib tidak ditemukan di df_findat_final: {missing_cols}")

    # Pastikan Year numeric int
    df[col_year] = pd.to_numeric(df[col_year], errors="coerce").astype("Int64")
    if df[col_year].isna().any():
        raise ValueError("Ada nilai Year yang tidak bisa dikonversi ke integer.")

    # -- BUAT UNTUK SEMUA TIPE INDIKATOR DISTRESS -- #
    # Dijadikan 1 dataset dengan kolom berbeda-beda #
    for distress_type in ["ppk", "neg_equity", "consecutive_loss"]:

        # Set nama kolom yang akan dibuat
        col_distress_year = f"tahun_distress_{distress_type}"
        col_target = f"target_distress_{distress_type}"

        if verbose:
            print("--- MEMPROSES DATA DENGAN KONFIGURASI BERIKUT ---")
            print(f"Tipe distress       : {distress_type}")
            print(f"Range tahun prediksi: {range_years}")
            print(f"Logika pelabelan    : {labeling_logic}")
            print(f"Shape awal          : {df.shape}")

        # ======================================================================
        # A) Hitung tahun_distress per ticker (distress_year_map: ticker -> distress year)
        # ======================================================================
        distress_year_map = pd.Series(dtype="Int64")

        if distress_type == "ppk":
            # Cek ketersediaan kolom wajid di df_emiten_idx
            for c in [col_kode, col_distress_ppk, col_tanggal_masuk]:
                if c not in df_emiten_idx.columns:
                    raise KeyError(f"Kolom '{c}' tidak ditemukan di df_emiten_idx")

            emiten = df_emiten_idx.copy()
            emiten[col_tanggal_masuk] = pd.to_datetime(emiten[col_tanggal_masuk], errors="coerce")

            # Cari yang Distress_PPK bernilai YES dan ada tanggal masuknya
            emiten_distress = emiten.loc[emiten[col_distress_ppk].astype(str).str.upper().eq("YES") & emiten[col_tanggal_masuk].notna(), [col_kode, col_tanggal_masuk]]

            # IF ada distress
            if not emiten_distress.empty:
                emiten_distress = emiten_distress.assign(_year=emiten_distress[col_tanggal_masuk].dt.year.astype("Int64"))
                distress_year_map = emiten_distress.groupby(col_kode)["_year"].min().astype("Int64")
            # ELSE (tidak ada distress)
            else:
                distress_year_map = pd.Series(dtype="Int64")

        elif distress_type == "neg_equity":
            # Cek ketersediaan kolom ekuitas di df_findat_final
            if col_equity not in df.columns:
                raise KeyError(f"Kolom '{col_equity}' tidak ditemukan di df_findat_final")

            # Pastikan numerik dan cari yang negatif && tidak null
            equity = pd.to_numeric(df[col_equity], errors="coerce")
            mask_neg = equity.notna() & (equity < 0)

            # distress year = tahun pertama equity negatif yang TEROBSERVASI (non-null)
            if mask_neg.any():
                distress_year_map = (
                    df.loc[mask_neg]
                    .groupby(col_ticker)[col_year]
                    .min()
                    .astype("Int64")
                )
            else:
                distress_year_map = pd.Series(dtype="Int64")

        else:  # distress_type == "consecutive_loss"
            # Cek ketersediaan kolom Net Income di df_findat_final
            if col_net_income not in df.columns:
                raise KeyError(f"Kolom '{col_net_income}' tidak ditemukan di df_findat_final")

            # pastikan numerik
            tmp = df[[col_ticker, col_year, col_net_income]].copy()
            tmp[col_net_income] = pd.to_numeric(tmp[col_net_income], errors="coerce")

            # sort berdasarkan ticker-year agar shift benar
            tmp = tmp.sort_values([col_ticker, col_year])

            # cari nilai prev year dan prev net income untuk tiap ticker-year
            prev_year = tmp.groupby(col_ticker)[col_year].shift(1)
            prev_ni = tmp.groupby(col_ticker)[col_net_income].shift(1)

            # distress di tahun y JIKA DAN HANYA JIKA y dan y-1 ada dan berurutan DAN NI(y) & NI(y-1) non-null negatif
            cond = (
                tmp[col_net_income].notna() & # net income tahun tsb ada
                prev_ni.notna() & # net income tahun sebelumnya ada
                prev_year.notna() & # tahun sebelumnya ada
                ((tmp[col_year] - prev_year) == 1) & # selisih tahun tsb dan tahun sbelumnya 1 (berturut-turut)
                (tmp[col_net_income] < 0) & # net income tahun tsb negatif
                (prev_ni < 0) # net income tahun sebelumnya negatif
            )

            if cond.any(): # ditemukan distress
                distress_year_map = (
                    tmp.loc[cond]
                    .groupby(col_ticker)[col_year]
                    .min()
                    .astype("Int64")
                )
            else: # tidak ditemukan distress
                distress_year_map = pd.Series(dtype="Int64")

        # Assign tahun_distress ke semua baris
        df[col_distress_year] = df[col_ticker].map(distress_year_map).astype("Int64")

        # ======================================================================
        # B) Inisialisasi target_distress (0/1) lalu apply 'censoring + ambiguity' dengan NA
        # ======================================================================

        # buat kolom target, isi 0 semua dulu
        df[col_target] = pd.Series(0, index=df.index, dtype="Int64")

        t = df[col_year].astype(int) # tahun data
        d = df[col_distress_year]    # tahun distress Int64 (nullable)
        r = range_years              # rentang tahun prediksi

        t_arr = df[col_year].to_numpy(dtype=float)            # e.g. [2019., 2020., ...] kolom berisi tahun, tidak ada null
        d_arr = df[col_distress_year].to_numpy(dtype=float)   # e.g. [2023., nan, ...] kolom berisi tahun distress, null artinya tidak pernah distress
        has_d = ~np.isnan(d_arr)

        if labeling_logic == "within":
            mask_one = has_d & (d_arr >= (t_arr + 1)) & (d_arr <= (t_arr + r)) # ada distress, terjadi antara t+1 hingga t+r inklusif
        else:  # labeling_logic == "in_exactly"
            mask_one = has_d & (d_arr == (t_arr + r)) # ada distress, distress terjadi tepat di t+r

        df.loc[mask_one, col_target] = 1 # baris yang memenuhi mask sblmnya diisi 1 pada kolom target

        # ======================================================================
        # C) "No recovery" censoring: set NA untuk tahun-tahun yang tidak dipakai
        # ======================================================================

        if labeling_logic == "within":
            # di-NA-kan untuk yang Year >= tahun_distress
            mask_censor = has_d & (t_arr >= d_arr) # d.notna() & (t >= d.astype(int))
        else: # labeling_logic == "in_exactly"
            # di-NA-kan untuk yang Year >= (tahun_distress - r + 1)
            mask_censor = has_d & (t_arr >= (d_arr - r + 1)) # d.notna() & (t >= (d.astype(int) - r + 1))

        df.loc[mask_censor, col_target] = pd.NA

        # ======================================================================
        # D) Ambiguity / observability: hanya mempengaruhi label 0 (kalau tidak pasti => NA)
        #    - within: butuh semua tahun t+1..t+r harus observable
        #    - in_exactly: butuh tahun t+r saja yang harus observable
        # ======================================================================
        # Hanya cek baris yang saat ini masih 0 (bukan 1, bukan NA)
        mask_zero = df[col_target].eq(0)

        if mask_zero.any():
            if distress_type == "ppk":
                # Kalau ticker tidak ada di df_emiten_idx, tidak observable.
                known_tickers = set(df_emiten_idx[col_kode].astype(str).unique()) if col_kode in df_emiten_idx.columns else set()
                ticker_known = df[col_ticker].astype(str).isin(known_tickers)

                if labeling_logic == "within":
                    observability_ok = (
                        ticker_known & # ticker harus known
                        ((t + 1) >= ppk_year_min) & # year + 1 harus >= ppk tersedia terkecil
                        ((t + r) <= ppk_year_max) # year + range harus <= ppk tersedia terbesar
                    )
                else: # labeling_logic == "in_exactly"
                    observability_ok = (
                        ticker_known & # ticker harus known
                        ((t + r) >= ppk_year_min) & # year + range harus >= ppk tersedia terkecil
                        ((t + r) <= ppk_year_max) # year + range harus <= ppk tersedia terbesar
                    )

                # pada baris yang kolom target 0 dan tidak observable, isi null
                df.loc[mask_zero & ~observability_ok, col_target] = pd.NA

                # REVISI untuk within, label 1 juga wajib observable (kalau tidak -> NA)
                # bertujuan agar tahun-tahun akhir tidak menumpuk label distress semua, proporsi distress train-test lebih setara
                # karena baris dg tahun-tahun terakhir tidak memiliki r tahun ke depan
                if labeling_logic == "within":
                    df.loc[df[col_target].eq(1) & ~observability_ok, col_target] = pd.NA

            elif distress_type == "neg_equity":
                equity = pd.to_numeric(df[col_equity], errors="coerce") # pastikan equity numeric
                df_equity_observable = df.loc[equity.notna(), [col_ticker, col_year]].copy() # ticker-year yang equity tidak null, bisa ditentukan distressnya

                # MultiIndex pasangan (ticker, year) yang OBSERVABLE untuk equity
                index_equity_observable = pd.MultiIndex.from_frame(df_equity_observable.astype({col_year: int}))

                if labeling_logic == "within":
                    observability_ok = np.ones(len(df), dtype=bool) # isi 1 semua dulu
                    for k in range(1, r + 1): # cek untuk tiap r dari 1 sampai r -> ( tahun target t+1, t+2, ... , t+r )
                        candidate = pd.MultiIndex.from_arrays([df[col_ticker].astype(str), (t + k).astype(int)]) # buat multiindex pair ticker-target(t+k)
                        observability_ok &= candidate.isin(index_equity_observable) # cek apakah ada di observable, jika iya maka boolean diakumulasi ke observability_ok
                    observability_ok = pd.Series(observability_ok, index=df.index) # ubah ke series agar indeksnya sama dengan df
                else: # labeling_logic == "in_exactly"
                    candidate = pd.MultiIndex.from_arrays([df[col_ticker].astype(str), (t + r).astype(int)]) # hanya cek t+r
                    observability_ok = pd.Series(candidate.isin(index_equity_observable), index=df.index) # cek apakah observable

                df.loc[mask_zero & ~observability_ok, col_target] = pd.NA # yang target 0 dan tidak observable, maka diset null

                # REVISI untuk within, label 1 juga wajib observable (kalau tidak -> NA)
                # bertujuan agar tahun-tahun akhir tidak menumpuk label distress semua, proporsi distress train-test lebih setara
                # karena baris dg tahun-tahun terakhir tidak memiliki r tahun ke depan
                if labeling_logic == "within":
                    df.loc[df[col_target].eq(1) & ~observability_ok, col_target] = pd.NA

            else:  # distress_type == "consecutive_loss"
                ni = pd.to_numeric(df[col_net_income], errors="coerce") # pastikan net income numeric
                df_ni_observable = df.loc[ni.notna(), [col_ticker, col_year]].copy() # ticker-year yang net income tidak null, bisa ditentukan distressnya

                # MultiIndex pasangan (ticker, year) yang OBSERVABLE untuk NI (row ada & NI non-null)
                index_ni_observable = pd.MultiIndex.from_frame(df_ni_observable.astype({col_year: int}))

                if labeling_logic == "within":
                    observability_ok = np.ones(len(df), dtype=bool) # isi 1 semua dulu
                    for k in range(1, r + 1): # cek untuk tiap r dari 1 sampai r -> ( tahun target t+1, t+2, ... , t+r )
                        y = (t + k).astype(int) # tahun target t+k
                        y_prev = (t + k - 1).astype(int) # tahun sebelumnya

                        candidate_y = pd.MultiIndex.from_arrays([df[col_ticker].astype(str), y]) # multiindex ticker dan y
                        candidate_prev = pd.MultiIndex.from_arrays([df[col_ticker].astype(str), y_prev]) # multiindex ticker dan y_prev

                        # distress(y) observable hanya jika NI(y) dan NI(y-1) observable
                        observability_ok &= candidate_y.isin(index_ni_observable) & candidate_prev.isin(index_ni_observable) # cek apakah y dan yprev ada di observable, jika iya maka boolean diakumulasi ke observability_ok

                    observability_ok = pd.Series(observability_ok, index=df.index) # sesuaikan index
                else: # labeling_logic == "in_exactly" (cek hanya untuk tepat r tahun setelah t)
                    y = (t + r).astype(int) # tahun target t+k
                    y_prev = (t + r - 1).astype(int) # tahun sebelum target

                    candidate_y = pd.MultiIndex.from_arrays([df[col_ticker].astype(str), y]) # multiindex ticker dan y
                    candidate_prev = pd.MultiIndex.from_arrays([df[col_ticker].astype(str), y_prev]) # multiindex ticker dan yprev

                    observability_ok = pd.Series(candidate_y.isin(index_ni_observable) & candidate_prev.isin(index_ni_observable), index=df.index) # cek apakah y dan yprev ada di observable, masukkan ke observability_ok

                df.loc[mask_zero & ~observability_ok, col_target] = pd.NA # yang targetnya 0 dan observabilty tidak ok, maka null

                # REVISI untuk within, label 1 juga wajib observable (kalau tidak -> NA)
                # bertujuan agar tahun-tahun akhir tidak menumpuk label distress semua, proporsi distress train-test lebih setara
                # karena baris dg tahun-tahun terakhir tidak memiliki r tahun ke depan
                if labeling_logic == "within":
                    df.loc[df[col_target].eq(1) & ~observability_ok, col_target] = pd.NA

        # Urutkan Kolom
        first_cols = [col_ticker, col_year, 'Entity Name', col_equity, col_net_income, col_target, col_distress_year]
        df = df[first_cols + [col for col in df.columns if col not in first_cols]]

        if verbose:
            vc = df[col_target].value_counts(dropna=False)
            print("--- RINGKASAN HASIL LABELING ---")
            print(vc)
            print()

    return df

CONFIG Target & Range Prediksi

RANGE = 2
# LABELING_LOGIC = 'in_exactly'
LABELING_LOGIC = 'within'

df_integrated_ppk = distress_target_labeling_and_adjustment(
                          df_findat_final=df_findat_final,
                          df_emiten_idx=df_emiten_idx,
                          range_years=RANGE,
                          distress_type='ppk',
                          labeling_logic=LABELING_LOGIC)

df_integrated_neg_eq = distress_target_labeling_and_adjustment(
                          df_findat_final=df_findat_final,
                          df_emiten_idx=df_emiten_idx,
                          range_years=RANGE,
                          distress_type='neg_equity',
                          labeling_logic=LABELING_LOGIC)

df_integrated_2_loss = distress_target_labeling_and_adjustment(
                          df_findat_final=df_findat_final,
                          df_emiten_idx=df_emiten_idx,
                          range_years=RANGE,
                          distress_type='consecutive_loss',
                          labeling_logic=LABELING_LOGIC)
--- MEMPROSES DATA DENGAN KONFIGURASI BERIKUT ---
Tipe distress       : ppk
Range tahun prediksi: 2
Logika pelabelan    : within
Shape awal          : (8489, 40)
--- RINGKASAN HASIL LABELING ---
target_distress
<NA>    5906
0       2073
1        510
Name: count, dtype: Int64

--- MEMPROSES DATA DENGAN KONFIGURASI BERIKUT ---
Tipe distress       : neg_equity
Range tahun prediksi: 2
Logika pelabelan    : within
Shape awal          : (8489, 40)
--- RINGKASAN HASIL LABELING ---
target_distress
0       6287
<NA>    2086
1        116
Name: count, dtype: Int64

--- MEMPROSES DATA DENGAN KONFIGURASI BERIKUT ---
Tipe distress       : consecutive_loss
Range tahun prediksi: 2
Logika pelabelan    : within
Shape awal          : (8489, 40)
--- RINGKASAN HASIL LABELING ---
target_distress
0       4595
<NA>    3329
1        565
Name: count, dtype: Int64
RANGE = 2
LABELING_LOGIC = 'within' # 'in_exactly'

df_integrated_all_indicators = distress_target_labeling_and_adjustment_all_indicators_at_once(
                                  df_findat_final=df_findat_final,
                                  df_emiten_idx=df_emiten_idx,
                                  range_years=RANGE,
                                  labeling_logic=LABELING_LOGIC)
--- MEMPROSES DATA DENGAN KONFIGURASI BERIKUT ---
Tipe distress       : ppk
Range tahun prediksi: 2
Logika pelabelan    : within
Shape awal          : (8489, 40)
--- RINGKASAN HASIL LABELING ---
target_distress_ppk
<NA>    5906
0       2073
1        510
Name: count, dtype: Int64

--- MEMPROSES DATA DENGAN KONFIGURASI BERIKUT ---
Tipe distress       : neg_equity
Range tahun prediksi: 2
Logika pelabelan    : within
Shape awal          : (8489, 42)
--- RINGKASAN HASIL LABELING ---
target_distress_neg_equity
0       6287
<NA>    2086
1        116
Name: count, dtype: Int64

--- MEMPROSES DATA DENGAN KONFIGURASI BERIKUT ---
Tipe distress       : consecutive_loss
Range tahun prediksi: 2
Logika pelabelan    : within
Shape awal          : (8489, 44)
--- RINGKASAN HASIL LABELING ---
target_distress_consecutive_loss
0       4595
<NA>    3329
1        565
Name: count, dtype: Int64

Uji Hasil

def audit_distress_labeling_suite(
    df_findat_final: pd.DataFrame,
    df_emiten_idx: pd.DataFrame | None = None,
    ranges=(1, 2),
    distress_types=("ppk", "neg_equity", "consecutive_loss"),
    labeling_logics=("within", "in_exactly"),
    labeling_fn=None,  # default: distress_target_labeling_and_adjustment kalau ada di scope
    precomputed: dict | None = None,  # optional: {(distress_type, labeling_logic, r): df_out}
    col_ticker: str = "ticker",
    col_year: str = "Year",
    col_equity: str = "Total Equity (Rp.M)",
    col_net_income: str = "Net Income to Company (Rp.M)",
    col_distress_year: str = "tahun_distress",
    col_target: str = "target_distress",
    # PPK specifics
    col_kode: str = "Kode",
    col_tanggal_masuk: str = "Tanggal Masuk",
    col_distress_ppk: str = "Distress_PPK",
    ppk_year_min: int = 2021,
    ppk_year_max: int = 2025,
    # audit behavior
    sample_rows: int = 8,
    recompute_distress_year: bool = True,  # cek ulang tahun_distress
):
    """
    Audit konsistensi output labeling untuk berbagai konfigurasi.
    Return:
      - summary_df: ringkasan jumlah pelanggaran per aturan per konfigurasi
      - issues: dict contoh baris pelanggaran untuk tiap konfigurasi
    """
    if labeling_fn is None:
        labeling_fn = distress_target_labeling_and_adjustment  # harus ada di scope notebook

    def _sample(df, n=sample_rows):
        if df is None or len(df) == 0:
            return df
        return df.head(n)

    def _recompute_distress_year(df_out, distress_type):
        """Recompute tahun_distress dari df_out + (df_emiten_idx kalau ppk). Return Series map per ticker."""
        if distress_type == "neg_equity":
            eq = pd.to_numeric(df_out[col_equity], errors="coerce")
            mask = eq.notna() & (eq < 0)
            if not mask.any():
                return pd.Series(dtype="Int64")
            return df_out.loc[mask].groupby(col_ticker)[col_year].min().astype("Int64")

        if distress_type == "consecutive_loss":
            tmp = df_out[[col_ticker, col_year, col_net_income]].copy()
            tmp[col_net_income] = pd.to_numeric(tmp[col_net_income], errors="coerce")
            tmp = tmp.sort_values([col_ticker, col_year])
            prev_year = tmp.groupby(col_ticker)[col_year].shift(1)
            prev_ni = tmp.groupby(col_ticker)[col_net_income].shift(1)
            cond = (
                tmp[col_net_income].notna() &
                prev_ni.notna() &
                prev_year.notna() &
                ((tmp[col_year] - prev_year) == 1) &
                (tmp[col_net_income] < 0) &
                (prev_ni < 0)
            )
            if not cond.any():
                return pd.Series(dtype="Int64")
            return tmp.loc[cond].groupby(col_ticker)[col_year].min().astype("Int64")

        if distress_type == "ppk":
            if df_emiten_idx is None:
                return pd.Series(dtype="Int64")
            em = df_emiten_idx.copy()
            if col_tanggal_masuk in em.columns:
                em[col_tanggal_masuk] = pd.to_datetime(em[col_tanggal_masuk], errors="coerce")
            mask = (
                em[col_distress_ppk].astype(str).str.upper().eq("YES") &
                em[col_tanggal_masuk].notna()
            )
            em_dist = em.loc[mask, [col_kode, col_tanggal_masuk]].copy()
            if em_dist.empty:
                return pd.Series(dtype="Int64")
            em_dist["_year"] = em_dist[col_tanggal_masuk].dt.year.astype("Int64")
            return em_dist.groupby(col_kode)["_year"].min().astype("Int64")

        return pd.Series(dtype="Int64")

    def _audit_single(df_out, distress_type, labeling_logic, r):
        """Return (summary_row_dict, issues_dict)."""
        issues = {}

        # basic columns check
        need_cols = {col_ticker, col_year, col_distress_year, col_target}
        miss = [c for c in need_cols if c not in df_out.columns]
        if miss:
            return (
                dict(
                    distress_type=distress_type, labeling_logic=labeling_logic, range_years=r,
                    n_rows=len(df_out),
                    error=f"Missing columns: {miss}",
                ),
                {"missing_columns": miss},
            )

        df_chk = df_out.copy()
        df_chk[col_year] = pd.to_numeric(df_chk[col_year], errors="coerce")
        t_arr = df_chk[col_year].to_numpy(dtype=float)
        d_arr = pd.to_numeric(df_chk[col_distress_year], errors="coerce").to_numpy(dtype=float)

        target = df_chk[col_target]
        # handle target possibly object -> numeric Int64-like comparisons
        # (if target already Int64, this is fine)
        is1 = target.eq(1)
        is0 = target.eq(0)
        isna = target.isna()

        has_d = ~np.isnan(d_arr)

        # -------- Rule checks --------
        # R1: target==1 => d exists
        v_r1 = is1 & ~has_d
        issues["R1_target1_requires_distress_year"] = _sample(df_chk.loc[v_r1, [col_ticker, col_year, col_distress_year, col_target]])

        # R2: target==1 must satisfy window condition
        if labeling_logic == "within":
            ok_r2 = (d_arr >= (t_arr + 1)) & (d_arr <= (t_arr + r))
        else:
            ok_r2 = (d_arr == (t_arr + r))
        v_r2 = is1 & ~ok_r2
        issues["R2_target1_window_mismatch"] = _sample(df_chk.loc[v_r2, [col_ticker, col_year, col_distress_year, col_target]])

        # R3: target==0 => (d NA) OR (d > t+r)
        ok_r3 = (~has_d) | (d_arr > (t_arr + r))
        v_r3 = is0 & ~ok_r3
        issues["R3_target0_inconsistent_with_distress_year"] = _sample(df_chk.loc[v_r3, [col_ticker, col_year, col_distress_year, col_target]])

        # R4: censoring no-recovery
        if labeling_logic == "within":
            should_be_na = has_d & (t_arr >= d_arr)
        else:
            should_be_na = has_d & (t_arr >= (d_arr - r + 1))
        v_r4 = (~isna) & should_be_na
        issues["R4_no_recovery_censor_violations"] = _sample(df_chk.loc[v_r4, [col_ticker, col_year, col_distress_year, col_target]])

        # R5: anchor year exists => must be 1
        # within: Year in [d-r, d-1] => target should be 1 (if row exists and not censored)
        # exact: Year == d-r => target should be 1 (if row exists)
        # We'll check row-level using d_arr, not groupby.
        if labeling_logic == "within":
            in_anchor = has_d & (t_arr >= (d_arr - r)) & (t_arr <= (d_arr - 1))
            v_r5 = in_anchor & ~is1 & ~isna  # if NA due to other reasons, still suspicious, but we won't flag NA here
        else:
            in_anchor = has_d & (t_arr == (d_arr - r))
            v_r5 = in_anchor & ~is1  # should never be 0/NA per current function logic
        issues["R5_anchor_year_should_be_1"] = _sample(df_chk.loc[v_r5, [col_ticker, col_year, col_distress_year, col_target]])

        # R6: observability for rows labeled 0 (strong check)
        # Only possible if we have required columns / df_emiten_idx for ppk
        v_r6 = pd.Series(False, index=df_chk.index)

        if distress_type == "ppk":
            if df_emiten_idx is not None and col_kode in df_emiten_idx.columns:
                known = set(df_emiten_idx[col_kode].astype(str).unique())
                ticker_known = df_chk[col_ticker].astype(str).isin(known).to_numpy()
                if labeling_logic == "within":
                    obs_ok = ticker_known & ((t_arr + 1) >= ppk_year_min) & ((t_arr + r) <= ppk_year_max)
                else:
                    obs_ok = ticker_known & ((t_arr + r) >= ppk_year_min) & ((t_arr + r) <= ppk_year_max)
                v_r6 = is0 & ~obs_ok

        elif distress_type == "neg_equity":
            if col_equity in df_chk.columns:
                eq = pd.to_numeric(df_chk[col_equity], errors="coerce")
                df_obs = df_chk.loc[eq.notna(), [col_ticker, col_year]].copy()
                idx_obs = pd.MultiIndex.from_frame(df_obs.astype({col_year: int}))

                if labeling_logic == "within":
                    ok = np.ones(len(df_chk), dtype=bool)
                    for k in range(1, r + 1):
                        cand = pd.MultiIndex.from_arrays([df_chk[col_ticker].astype(str), (df_chk[col_year].astype(int) + k)])
                        ok &= cand.isin(idx_obs)
                    v_r6 = is0 & ~ok
                else:
                    cand = pd.MultiIndex.from_arrays([df_chk[col_ticker].astype(str), (df_chk[col_year].astype(int) + r)])
                    ok = cand.isin(idx_obs)
                    v_r6 = is0 & ~ok

        elif distress_type == "consecutive_loss":
            if col_net_income in df_chk.columns:
                ni = pd.to_numeric(df_chk[col_net_income], errors="coerce")
                df_obs = df_chk.loc[ni.notna(), [col_ticker, col_year]].copy()
                idx_obs = pd.MultiIndex.from_frame(df_obs.astype({col_year: int}))

                if labeling_logic == "within":
                    ok = np.ones(len(df_chk), dtype=bool)
                    base_year = df_chk[col_year].astype(int)
                    for k in range(1, r + 1):
                        y = base_year + k
                        y_prev = base_year + k - 1
                        cand_y = pd.MultiIndex.from_arrays([df_chk[col_ticker].astype(str), y])
                        cand_prev = pd.MultiIndex.from_arrays([df_chk[col_ticker].astype(str), y_prev])
                        ok &= cand_y.isin(idx_obs) & cand_prev.isin(idx_obs)
                    v_r6 = is0 & ~ok
                else:
                    base_year = df_chk[col_year].astype(int)
                    y = base_year + r
                    y_prev = base_year + r - 1
                    cand_y = pd.MultiIndex.from_arrays([df_chk[col_ticker].astype(str), y])
                    cand_prev = pd.MultiIndex.from_arrays([df_chk[col_ticker].astype(str), y_prev])
                    ok = cand_y.isin(idx_obs) & cand_prev.isin(idx_obs)
                    v_r6 = is0 & ~ok

        issues["R6_target0_requires_observable_horizon"] = _sample(df_chk.loc[v_r6, [col_ticker, col_year, col_distress_year, col_target]])

        # R7: recompute tahun_distress and compare (optional)
        v_r7 = pd.Series(False, index=df_chk.index)
        if recompute_distress_year:
            recomputed_map = _recompute_distress_year(df_chk, distress_type)  # Series index=ticker->year
            if not recomputed_map.empty:
                # map to rows
                d2 = df_chk[col_ticker].map(recomputed_map)
                # Compare: treat both as numeric, allow NA==NA
                a = pd.to_numeric(df_chk[col_distress_year], errors="coerce")
                b = pd.to_numeric(d2, errors="coerce")
                v_r7 = ~(a.fillna(-999999).eq(b.fillna(-999999)))
            else:
                # if recomputed empty, then all years should be NA
                a = pd.to_numeric(df_chk[col_distress_year], errors="coerce")
                v_r7 = a.notna()

        issues["R7_tahun_distress_mismatch_recomputed"] = _sample(df_chk.loc[v_r7, [col_ticker, col_year, col_distress_year, col_target]])

        # summary counts
        summary = dict(
            distress_type=distress_type,
            labeling_logic=labeling_logic,
            range_years=r,
            n_rows=len(df_chk),
            n_target_1=int(is1.sum()),
            n_target_0=int(is0.sum()),
            n_target_na=int(isna.sum()),
            n_R1=int(v_r1.sum()),
            n_R2=int(v_r2.sum()),
            n_R3=int(v_r3.sum()),
            n_R4=int(v_r4.sum()),
            n_R5=int(v_r5.sum()),
            n_R6=int(v_r6.sum()),
            n_R7=int(v_r7.sum()) if recompute_distress_year else 0,
        )

        # Keep only non-empty issue samples to reduce clutter
        issues = {k: v for k, v in issues.items() if v is not None and len(v) > 0}
        return summary, issues

    summary_rows = []
    all_issues = {}

    for distress_type in distress_types:
        for labeling_logic in labeling_logics:
            for r in ranges:
                cfg = (distress_type, labeling_logic, int(r))

                if precomputed is not None and cfg in precomputed:
                    df_out = precomputed[cfg]
                else:
                    # call labeling function
                    if distress_type == "ppk":
                        if df_emiten_idx is None:
                            raise ValueError("df_emiten_idx wajib disediakan untuk distress_type='ppk'")
                        df_out = labeling_fn(
                            df_findat_final=df_findat_final,
                            df_emiten_idx=df_emiten_idx,
                            range_years=int(r),
                            distress_type=distress_type,
                            labeling_logic=labeling_logic,
                            verbose=False,
                        )
                    else:
                        # labeling_fn saat ini butuh df_emiten_idx sebagai argumen juga
                        df_out = labeling_fn(
                            df_findat_final=df_findat_final,
                            df_emiten_idx=df_emiten_idx if df_emiten_idx is not None else pd.DataFrame({col_kode: []}),
                            range_years=int(r),
                            distress_type=distress_type,
                            labeling_logic=labeling_logic,
                            verbose=False,
                        )

                summary, issues = _audit_single(df_out, distress_type, labeling_logic, int(r))
                summary_rows.append(summary)
                all_issues[cfg] = issues  # selalu simpan, meskipun kosong {}

    summary_df = pd.DataFrame(summary_rows).sort_values(["distress_type", "labeling_logic", "range_years"]).reset_index(drop=True)
    return summary_df, all_issues
summary, issues = audit_distress_labeling_suite(
    df_findat_final=df_findat_final,
    df_emiten_idx=df_emiten_idx,
    ranges=[1,2,3],
    distress_types=["ppk","neg_equity","consecutive_loss"],
    labeling_logics=["within","in_exactly"],
    sample_rows=10,
    recompute_distress_year=True
)

summary
# Kalau ada pelanggaran, lihat contohnya:
issues.keys()
cfg = ("neg_equity","in_exactly",2)


for cfg in issues.keys():
    print(f"Cek konfigurasi : {cfg}")
    if issues[cfg]:
        print(f"Jumlah baris kesalahan: {len(issues[cfg])}")
        display(issues[cfg])
    else:
        print("Tidak ada isu untuk konfigurasi ini.")
Cek konfigurasi : ('ppk', 'within', 1)
Tidak ada isu untuk konfigurasi ini.
Cek konfigurasi : ('ppk', 'within', 2)
Tidak ada isu untuk konfigurasi ini.
Cek konfigurasi : ('ppk', 'within', 3)
Tidak ada isu untuk konfigurasi ini.
Cek konfigurasi : ('ppk', 'in_exactly', 1)
Tidak ada isu untuk konfigurasi ini.
Cek konfigurasi : ('ppk', 'in_exactly', 2)
Tidak ada isu untuk konfigurasi ini.
Cek konfigurasi : ('ppk', 'in_exactly', 3)
Tidak ada isu untuk konfigurasi ini.
Cek konfigurasi : ('neg_equity', 'within', 1)
Tidak ada isu untuk konfigurasi ini.
Cek konfigurasi : ('neg_equity', 'within', 2)
Tidak ada isu untuk konfigurasi ini.
Cek konfigurasi : ('neg_equity', 'within', 3)
Tidak ada isu untuk konfigurasi ini.
Cek konfigurasi : ('neg_equity', 'in_exactly', 1)
Tidak ada isu untuk konfigurasi ini.
Cek konfigurasi : ('neg_equity', 'in_exactly', 2)
Tidak ada isu untuk konfigurasi ini.
Cek konfigurasi : ('neg_equity', 'in_exactly', 3)
Tidak ada isu untuk konfigurasi ini.
Cek konfigurasi : ('consecutive_loss', 'within', 1)
Tidak ada isu untuk konfigurasi ini.
Cek konfigurasi : ('consecutive_loss', 'within', 2)
Tidak ada isu untuk konfigurasi ini.
Cek konfigurasi : ('consecutive_loss', 'within', 3)
Tidak ada isu untuk konfigurasi ini.
Cek konfigurasi : ('consecutive_loss', 'in_exactly', 1)
Tidak ada isu untuk konfigurasi ini.
Cek konfigurasi : ('consecutive_loss', 'in_exactly', 2)
Tidak ada isu untuk konfigurasi ini.
Cek konfigurasi : ('consecutive_loss', 'in_exactly', 3)
Tidak ada isu untuk konfigurasi ini.
issues.keys()
dict_keys([('ppk', 'within', 1), ('ppk', 'within', 2), ('ppk', 'within', 3), ('ppk', 'in_exactly', 1), ('ppk', 'in_exactly', 2), ('ppk', 'in_exactly', 3), ('neg_equity', 'within', 1), ('neg_equity', 'within', 2), ('neg_equity', 'within', 3), ('neg_equity', 'in_exactly', 1), ('neg_equity', 'in_exactly', 2), ('neg_equity', 'in_exactly', 3), ('consecutive_loss', 'within', 1), ('consecutive_loss', 'within', 2), ('consecutive_loss', 'within', 3), ('consecutive_loss', 'in_exactly', 1), ('consecutive_loss', 'in_exactly', 2), ('consecutive_loss', 'in_exactly', 3)])
summary.query("distress_type=='neg_equity' and labeling_logic=='within' and range_years==2")
distress_type labeling_logic range_years n_rows n_target_1 n_target_0 n_target_na n_R1 n_R2 n_R3 n_R4 n_R5 n_R6 n_R7
10 neg_equity within 2 8489 116 6287 2086 0 0 0 0 0 0 0
df_integrated_ppk[(df_integrated_ppk['Year']<(2020)) & (df_integrated_ppk['target_distress'].notna())]
variable ticker Year Entity Name target_distress tahun_distress Total Equity (Rp.M) Net Income to Company (Rp.M) 1st Level Primary Industry Year Established Sector Industry Group Parent Percent Owned (%) Percent Owned - All Institutions (%) Percent Owned - Insiders (%) Total Liabilities (Rp.M) Total Assets (Rp.M) Current Ratio (x) Quick Ratio (x) Working Capital (Rp.M) Total Current Assets (Rp.M) Total Current Liabilities (Rp.M) Inventory (Rp.M) Prepaid Exp. (Rp.M) Long-term Debt (Rp.M) Short-term Borrowings (Rp.M) Current Portion of LT Debt & Leases (Rp.M) Total Debt (Rp.M) Net Property, Plant & Equipment (Rp.M) Cost Of Goods Sold (Rp.M) Total Revenue (Rp.M) Operating Income (Rp.M) EBITDA (Rp.M) EBIT (Rp.M) Cash from Ops. (Rp.M) Net Change in Cash (Rp.M) ECS Total Common Shares Outstanding (actual) Market Capitalization (Rp.M) Year Close Stock Price (Rp.) Total Capital (Rp.M) Cash & Short-term Investments (Rp.M) Net Intangibles (Rp.M) IPO Year

Cek hasil dataset integrated yang menggabungkan semua tipe distress.

Hasil harus sama dengan dataset yang tiap tipe distressnya dipisah.

print("Cek perbedaan antara dataset distress PPK yang dipisah dan digabung:")

pd.concat([df_integrated_ppk,
            (df_integrated_all_indicators
            .drop(columns=["target_distress_neg_equity", "target_distress_consecutive_loss",
                          "tahun_distress_neg_equity", "tahun_distress_consecutive_loss"], errors="ignore")
            .rename(columns={"target_distress_ppk": "target_distress",
                            "tahun_distress_ppk": "tahun_distress"}))
          ]).drop_duplicates(keep=False)
Cek perbedaan antara dataset distress PPK yang dipisah dan digabung:
variable ticker Year Entity Name target_distress tahun_distress Total Equity (Rp.M) Net Income to Company (Rp.M) 1st Level Primary Industry Year Established Sector Industry Group Parent Percent Owned (%) Percent Owned - All Institutions (%) Percent Owned - Insiders (%) Total Liabilities (Rp.M) Total Assets (Rp.M) Current Ratio (x) Quick Ratio (x) Working Capital (Rp.M) Total Current Assets (Rp.M) Total Current Liabilities (Rp.M) Inventory (Rp.M) Prepaid Exp. (Rp.M) Long-term Debt (Rp.M) Short-term Borrowings (Rp.M) Current Portion of LT Debt & Leases (Rp.M) Total Debt (Rp.M) Net Property, Plant & Equipment (Rp.M) Cost Of Goods Sold (Rp.M) Total Revenue (Rp.M) Operating Income (Rp.M) EBITDA (Rp.M) EBIT (Rp.M) Cash from Ops. (Rp.M) Net Change in Cash (Rp.M) ECS Total Common Shares Outstanding (actual) Market Capitalization (Rp.M) Year Close Stock Price (Rp.) Total Capital (Rp.M) Cash & Short-term Investments (Rp.M) Net Intangibles (Rp.M) IPO Year
print("Cek perbedaan antara dataset distress ekuitas negatif yang dipisah dan digabung:")

pd.concat([df_integrated_neg_eq,
            (df_integrated_all_indicators
            .drop(columns=["target_distress_ppk", "target_distress_consecutive_loss",
                          "tahun_distress_ppk", "tahun_distress_consecutive_loss"], errors="ignore")
            .rename(columns={"target_distress_neg_equity": "target_distress",
                            "tahun_distress_neg_equity": "tahun_distress"}))
          ]).drop_duplicates(keep=False)
Cek perbedaan antara dataset distress ekuitas negatif yang dipisah dan digabung:
variable ticker Year Entity Name target_distress tahun_distress Total Equity (Rp.M) Net Income to Company (Rp.M) 1st Level Primary Industry Year Established Sector Industry Group Parent Percent Owned (%) Percent Owned - All Institutions (%) Percent Owned - Insiders (%) Total Liabilities (Rp.M) Total Assets (Rp.M) Current Ratio (x) Quick Ratio (x) Working Capital (Rp.M) Total Current Assets (Rp.M) Total Current Liabilities (Rp.M) Inventory (Rp.M) Prepaid Exp. (Rp.M) Long-term Debt (Rp.M) Short-term Borrowings (Rp.M) Current Portion of LT Debt & Leases (Rp.M) Total Debt (Rp.M) Net Property, Plant & Equipment (Rp.M) Cost Of Goods Sold (Rp.M) Total Revenue (Rp.M) Operating Income (Rp.M) EBITDA (Rp.M) EBIT (Rp.M) Cash from Ops. (Rp.M) Net Change in Cash (Rp.M) ECS Total Common Shares Outstanding (actual) Market Capitalization (Rp.M) Year Close Stock Price (Rp.) Total Capital (Rp.M) Cash & Short-term Investments (Rp.M) Net Intangibles (Rp.M) IPO Year
print("Cek perbedaan antara dataset distress rugi 2 tahun yang dipisah dan digabung:")

pd.concat([df_integrated_2_loss,
            (df_integrated_all_indicators
            .drop(columns=["target_distress_neg_equity", "target_distress_ppk",
                          "tahun_distress_neg_equity", "tahun_distress_ppk"], errors="ignore")
            .rename(columns={"target_distress_consecutive_loss": "target_distress",
                            "tahun_distress_consecutive_loss": "tahun_distress"}))
          ]).drop_duplicates(keep=False)
Cek perbedaan antara dataset distress rugi 2 tahun yang dipisah dan digabung:
variable ticker Year Entity Name target_distress tahun_distress Total Equity (Rp.M) Net Income to Company (Rp.M) 1st Level Primary Industry Year Established Sector Industry Group Parent Percent Owned (%) Percent Owned - All Institutions (%) Percent Owned - Insiders (%) Total Liabilities (Rp.M) Total Assets (Rp.M) Current Ratio (x) Quick Ratio (x) Working Capital (Rp.M) Total Current Assets (Rp.M) Total Current Liabilities (Rp.M) Inventory (Rp.M) Prepaid Exp. (Rp.M) Long-term Debt (Rp.M) Short-term Borrowings (Rp.M) Current Portion of LT Debt & Leases (Rp.M) Total Debt (Rp.M) Net Property, Plant & Equipment (Rp.M) Cost Of Goods Sold (Rp.M) Total Revenue (Rp.M) Operating Income (Rp.M) EBITDA (Rp.M) EBIT (Rp.M) Cash from Ops. (Rp.M) Net Change in Cash (Rp.M) ECS Total Common Shares Outstanding (actual) Market Capitalization (Rp.M) Year Close Stock Price (Rp.) Total Capital (Rp.M) Cash & Short-term Investments (Rp.M) Net Intangibles (Rp.M) IPO Year

Pilih dan Copy Hasil Dataset Terintegrasi

# df_findat_integrated = df_integrated_ppk.copy()
# df_findat_integrated = df_integrated_neg_eq.copy()
# df_findat_integrated = df_integrated_2_loss.copy()
df_findat_integrated = df_integrated_all_indicators.copy()
# # temporary, cuma utk liat output kode2 setelahnya
# df_findat_integrated = df_integrated_ppk[df_integrated_ppk['target_distress'].notna()].copy()
# df_findat_integrated = df_findat_integrated.rename(columns={'target_distress': 'distress'})

Data Understanding

Sebaran Data

df_findat_integrated.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 8489 entries, 0 to 8488
Data columns (total 46 columns):
 #   Column                                        Non-Null Count  Dtype  
---  ------                                        --------------  -----  
 0   ticker                                        8489 non-null   object 
 1   Year                                          8489 non-null   Int64  
 2   Entity Name                                   8489 non-null   object 
 3   Total Equity (Rp.M)                           8489 non-null   float64
 4   Net Income to Company (Rp.M)                  8489 non-null   float64
 5   target_distress_consecutive_loss              5160 non-null   Int64  
 6   tahun_distress_consecutive_loss               4169 non-null   Int64  
 7   target_distress_neg_equity                    6403 non-null   Int64  
 8   tahun_distress_neg_equity                     1073 non-null   Int64  
 9   target_distress_ppk                           2583 non-null   Int64  
 10  tahun_distress_ppk                            3421 non-null   Int64  
 11  1st Level Primary Industry                    8489 non-null   object 
 12  Year Established                              8468 non-null   Int64  
 13  Sector                                        8489 non-null   object 
 14  Industry Group                                8489 non-null   object 
 15  Parent Percent Owned (%)                      4848 non-null   float64
 16  Percent Owned - All Institutions (%)          4542 non-null   float64
 17  Percent Owned - Insiders (%)                  5938 non-null   float64
 18  Total Liabilities (Rp.M)                      8489 non-null   float64
 19  Total Assets (Rp.M)                           8489 non-null   float64
 20  Current Ratio (x)                             7783 non-null   float64
 21  Quick Ratio (x)                               7785 non-null   float64
 22  Working Capital (Rp.M)                        8489 non-null   float64
 23  Total Current Assets (Rp.M)                   8489 non-null   float64
 24  Total Current Liabilities (Rp.M)              8489 non-null   float64
 25  Inventory (Rp.M)                              7283 non-null   float64
 26  Prepaid Exp. (Rp.M)                           7027 non-null   float64
 27  Long-term Debt (Rp.M)                         5908 non-null   float64
 28  Short-term Borrowings (Rp.M)                  5579 non-null   float64
 29  Current Portion of LT Debt & Leases (Rp.M)    337 non-null    float64
 30  Total Debt (Rp.M)                             8468 non-null   float64
 31  Net Property, Plant & Equipment (Rp.M)        8470 non-null   float64
 32  Cost Of Goods Sold (Rp.M)                     8309 non-null   float64
 33  Total Revenue (Rp.M)                          8458 non-null   float64
 34  Operating Income (Rp.M)                       8488 non-null   float64
 35  EBITDA (Rp.M)                                 7383 non-null   float64
 36  EBIT (Rp.M)                                   7390 non-null   float64
 37  Cash from Ops. (Rp.M)                         8487 non-null   float64
 38  Net Change in Cash (Rp.M)                     8487 non-null   float64
 39  ECS Total Common Shares Outstanding (actual)  8488 non-null   float64
 40  Market Capitalization (Rp.M)                  8464 non-null   float64
 41  Year Close Stock Price (Rp.)                  8464 non-null   float64
 42  Total Capital (Rp.M)                          8468 non-null   float64
 43  Cash & Short-term Investments (Rp.M)          8489 non-null   float64
 44  Net Intangibles (Rp.M)                        1763 non-null   float64
 45  IPO Year                                      8489 non-null   Int64  
dtypes: Int64(9), float64(32), object(5)
memory usage: 3.1+ MB
# =========================
# SETUP KOLOM (df_findat_integrated)
# =========================
df = df_findat_integrated

# Exclude:
# - ticker, Year, Entity Name
# - kolom awalan 'tahun' atau 'target'
exclude_exact = {'ticker', 'Year', 'Entity Name'}
exclude_prefixes = ('tahun', 'target')

cols_to_exclude = {
    c for c in df.columns
    if (c in exclude_exact) or c.startswith(exclude_prefixes)
}

potential_cols = [c for c in df.columns if c not in cols_to_exclude]

# kategorikal khusus
cat_cols = ['1st Level Primary Industry', 'Sector', 'Industry Group']
cat_cols = [c for c in cat_cols if c in df.columns]

# numerik selain kolom kategorikal
numeric_cols = [
    c for c in potential_cols
    if (c not in cat_cols) and pd.api.types.is_numeric_dtype(df[c])
]

print(f"Total Baris Data: {len(df)}")
print(f"Fitur Numerik: {len(numeric_cols)}")
print(f"Fitur Kategorikal (pie): {len(cat_cols)} -> {cat_cols}")

N = len(df)

# =========================
# VISUALISASI NUMERIK (Boxplot + Hist) - format sama persis
# =========================
features_per_page = 5
num_batches = math.ceil(len(numeric_cols) / features_per_page)

MAX_POINTS_PLOT = 80_000
MAX_POINTS_KDE  = 8_000
HIST_BINS       = 50

for i in range(num_batches):
    start = i * features_per_page
    end = min((i + 1) * features_per_page, len(numeric_cols))
    batch = numeric_cols[start:end]

    fig, axes = plt.subplots(len(batch), 2, figsize=(16, 4 * len(batch)))
    fig.suptitle(f'Distribusi Numerik Part {i+1}', fontsize=16, fontweight='bold', y=1.01)

    if len(batch) == 1:
        axes = [axes]

    for idx, col in enumerate(batch):
        ax_box = axes[idx][0]
        ax_hist = axes[idx][1]

        try:
            s_raw = df[col]

            # hitung null (tanpa mengubah df)
            null_count = int(s_raw.isna().sum())
            null_prop = null_count / N if N else 0.0

            # bersihkan untuk plotting: inf -> nan, coerce numeric, dropna sementara
            s_clean = (
                s_raw.replace([np.inf, -np.inf], np.nan)
                     .pipe(pd.to_numeric, errors="coerce")
                     .dropna()
            )

            if s_clean.empty:
                msg = f"ALL NULL/INF | Null: {null_count} ({null_prop:.1%})"
                ax_box.set_title(f"Outlier Check: {col}", fontsize=10, fontweight='bold')
                ax_box.text(0.5, 0.5, msg, ha='center', va='center')
                ax_hist.set_title(msg, fontsize=10)
                ax_hist.text(0.5, 0.5, "No data to plot", ha='center', va='center')
                continue

            # sampling agar tidak berat
            s_plot = s_clean.sample(MAX_POINTS_PLOT, random_state=42) if len(s_clean) > MAX_POINTS_PLOT else s_clean

            # statistik (dari non-null)
            desc = s_plot.describe()
            # skew = s_plot.skew()

            stats_text = (
                f"Min: {desc['min']:.2f} | Max: {desc['max']:.2f} | "
                f"Mean: {desc['mean']:.2f} | Med: {desc['50%']:.2f} | "
                # f"Skew: {skew:.2f} | "
                f"Null: {null_count} ({null_prop:.1%})"
            )

            # boxplot
            sns.boxplot(x=s_plot, ax=ax_box, color='#3498db')
            ax_box.set_title(f"Outlier Check: {col}", fontsize=10, fontweight='bold')
            ax_box.set_xlabel('')

            # hist (+ KDE dibatasi agar tidak hang)
            use_kde = (s_plot.nunique() > 1) and (len(s_plot) <= MAX_POINTS_KDE)
            sns.histplot(s_plot, bins=HIST_BINS, kde=use_kde, ax=ax_hist, color='#e67e22', stat='density')

            ax_hist.axvline(desc['mean'], color='red', linestyle='--', label='Mean')
            ax_hist.axvline(desc['50%'], color='green', linestyle='-', label='Median')
            ax_hist.set_title(stats_text, fontsize=10)
            ax_hist.legend(loc='upper right', fontsize='small')
            ax_hist.set_xlabel('')

        except Exception as e:
            err = f"{type(e).__name__}: {e}"
            ax_box.clear()
            ax_hist.clear()
            ax_box.set_title(f"Outlier Check: {col}", fontsize=10, fontweight='bold')
            ax_box.text(0.5, 0.5, f"Error:\n{err}", ha='center', va='center', wrap=True)
            ax_hist.set_title(f"Error on: {col}", fontsize=10)
            ax_hist.text(0.5, 0.5, f"Error:\n{err}", ha='center', va='center', wrap=True)

    plt.tight_layout()
    plt.show()
    plt.close(fig)
    plt.close('all')

# =========================
# VISUALISASI KATEGORIKAL (Pie Chart)
# =========================
# - tampilkan jumlah + persentase
# - termasuk NaN kalau ada
# - diasumsikan nunique <= 30 (sesuai requirement)
if len(cat_cols) > 0:
    for col in cat_cols:
        s = df[col]

        # value_counts termasuk NaN
        vc = s.value_counts(dropna=False)

        # label NaN agar terlihat
        labels = [("NaN" if pd.isna(x) else str(x)) for x in vc.index]
        sizes = vc.values
        total = sizes.sum()

        # autopct yang menampilkan count + percent
        def autopct_count_pct(pct):
            count = int(round(pct * total / 100.0))
            return f"{count}\n({pct:.1f}%)"

        fig, ax = plt.subplots(figsize=(10, 7))
        wedges, texts, autotexts = ax.pie(
            sizes,
            labels=labels,
            autopct=autopct_count_pct,
            startangle=90,
            textprops={'fontsize': 10}
        )
        ax.set_title(f"{col} | Total: {total} | Null: {int(s.isna().sum())} ({(s.isna().mean() if total else 0):.1%})",
                     fontsize=14, fontweight='bold')
        ax.axis('equal')  # pie jadi bulat
        plt.tight_layout()
        plt.show()
        plt.close(fig)
        plt.close('all')
Total Baris Data: 8489
Fitur Numerik: 34
Fitur Kategorikal (pie): 3 -> ['1st Level Primary Industry', 'Sector', 'Industry Group']

cat_cols = ['1st Level Primary Industry', 'Sector', 'Industry Group']
df = df_findat_integrated[cat_cols].copy().fillna("NaN")

# urutkan kolom: unik paling sedikit -> paling banyak (kiri -> kanan)
order = sorted(cat_cols, key=lambda c: df[c].nunique(dropna=False))
left_col, mid_col, right_col = order

# urutkan kategori kiri dari count terbesar
left_cats = df[left_col].value_counts(dropna=False).index.tolist()

# untuk level lain: biarkan urut by frequency juga (rapi, simple)
mid_cats = df[mid_col].value_counts(dropna=False).index.tolist()
right_cats = df[right_col].value_counts(dropna=False).index.tolist()

def node_id(col, val):   return f"{col}||{val}"   # unik (internal)
def node_label(val):     return str(val)          # yang ditampilkan (tanpa nama kolom)

node_ids = (
    [node_id(left_col, v) for v in left_cats] +
    [node_id(mid_col, v) for v in mid_cats] +
    [node_id(right_col, v) for v in right_cats]
)
node_labels = (
    [node_label(v) for v in left_cats] +
    [node_label(v) for v in mid_cats] +
    [node_label(v) for v in right_cats]
)
id_to_index = {nid: i for i, nid in enumerate(node_ids)}

# hitung flow kiri->tengah dan tengah->kanan
flow_lm = df.groupby([left_col, mid_col]).size().reset_index(name="value")
flow_mr = df.groupby([mid_col, right_col]).size().reset_index(name="value")

sources, targets, values = [], [], []

for _, r in flow_lm.iterrows():
    sources.append(id_to_index[node_id(left_col, r[left_col])])
    targets.append(id_to_index[node_id(mid_col,  r[mid_col])])
    values.append(int(r["value"]))

for _, r in flow_mr.iterrows():
    sources.append(id_to_index[node_id(mid_col,   r[mid_col])])
    targets.append(id_to_index[node_id(right_col, r[right_col])])
    values.append(int(r["value"]))

fig = go.Figure(
    data=[go.Sankey(
        arrangement="snap",
        node=dict(
            label=node_labels,
            pad=12,
            thickness=16
        ),
        link=dict(
            source=sources,
            target=targets,
            value=values
        )
    )]
)

fig.update_layout(
    # title=f"{left_col} → {mid_col} → {right_col} (n={len(df)})",
    title={
        "text": "Alur Sankey Diagram Kolom Sektor/Industri per BARIS DATA",
        "x": 0.5,          # center
        "xanchor": "center"
    },
    font_size=11,
    height=1100,  # perpanjang ke bawah supaya lebih lega
    annotations=[
        dict(x=0.02, y=1.03, xref="paper", yref="paper",
             text=f"<b>{left_col}</b>", showarrow=False, xanchor="left"),
        dict(x=0.50, y=1.03, xref="paper", yref="paper",
             text=f"<b>{mid_col}</b>", showarrow=False, xanchor="center"),
        dict(x=0.98, y=1.03, xref="paper", yref="paper",
             text=f"<b>{right_col}</b>", showarrow=False, xanchor="right"),
    ]
)

# fig.show(renderer="notebook")
fig.show(renderer="svg")

Analisis dan Eksplorasi Data

pd.set_option('display.max_columns', None)
pd.set_option('display.width', 220)
sns.set_theme(style='whitegrid')

df = df_findat_integrated.copy()

# DEFINISI KELOMPOK KOLOM
# ---------------------------------------------------------------------
id_cols = ['ticker', 'Year', 'Entity Name']
categorical_cols = ['1st Level Primary Industry', 'Sector', 'Industry Group']
date_year_cols = ['Date Established', 'Year Established', 'IPO Year']
ownership_cols = [
    'Parent Percent Owned (%)',
    'Percent Owned - All Institutions (%)',
    'Percent Owned - Insiders (%)',
]
target_cols = [
    'target_distress_ppk',
    'target_distress_neg_equity',
    'target_distress_consecutive_loss',
]
year_distress_cols = [
    'tahun_distress_ppk',
    'tahun_distress_neg_equity',
    'tahun_distress_consecutive_loss',
]
# Kolom akun keuangan numerik = seluruh kolom numerik di luar kelompok di atas
non_account = set(id_cols + categorical_cols + date_year_cols +
                  target_cols + year_distress_cols + ownership_cols)
account_cols = [
    c for c in df.columns
    if c not in non_account and pd.api.types.is_numeric_dtype(df[c])
]

print("Jumlah kolom akun keuangan numerik:", len(account_cols))
print(account_cols)
Jumlah kolom akun keuangan numerik: 29
['Total Equity (Rp.M)', 'Net Income to Company (Rp.M)', 'Total Liabilities (Rp.M)', 'Total Assets (Rp.M)', 'Current Ratio (x)', 'Quick Ratio (x)', 'Working Capital (Rp.M)', 'Total Current Assets (Rp.M)', 'Total Current Liabilities (Rp.M)', 'Inventory (Rp.M)', 'Prepaid Exp. (Rp.M)', 'Long-term Debt (Rp.M)', 'Short-term Borrowings (Rp.M)', 'Current Portion of LT Debt & Leases (Rp.M)', 'Total Debt (Rp.M)', 'Net Property, Plant & Equipment (Rp.M)', 'Cost Of Goods Sold (Rp.M)', 'Total Revenue (Rp.M)', 'Operating Income (Rp.M)', 'EBITDA (Rp.M)', 'EBIT (Rp.M)', 'Cash from Ops. (Rp.M)', 'Net Change in Cash (Rp.M)', 'ECS Total Common Shares Outstanding (actual)', 'Market Capitalization (Rp.M)', 'Year Close Stock Price (Rp.)', 'Total Capital (Rp.M)', 'Cash & Short-term Investments (Rp.M)', 'Net Intangibles (Rp.M)']

Gambaran Umum Dataset

# GAMBARAN UMUM DATASET
# ---------------------------------------------------------------------
print("Dimensi dataset      :", df.shape)
print("Jumlah perusahaan unik:", df['ticker'].nunique())
print("Rentang tahun fiskal :", int(df['Year'].min()), "-", int(df['Year'].max()))

print("\nJumlah baris per tahun fiskal:")
print(df['Year'].value_counts().sort_index())

print("\nInfo tipe data:")
df.info()
Dimensi dataset      : (8489, 46)
Jumlah perusahaan unik: 853
Rentang tahun fiskal : 2008 - 2024

Jumlah baris per tahun fiskal:
Year
2008    302
2009    315
2010    325
2011    344
2012    367
2013    392
2014    417
2015    441
2016    460
2017    475
2018    508
2019    559
2020    611
2021    661
2022    715
2023    765
2024    832
Name: count, dtype: Int64

Info tipe data:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 8489 entries, 0 to 8488
Data columns (total 46 columns):
 #   Column                                        Non-Null Count  Dtype  
---  ------                                        --------------  -----  
 0   ticker                                        8489 non-null   object 
 1   Year                                          8489 non-null   Int64  
 2   Entity Name                                   8489 non-null   object 
 3   Total Equity (Rp.M)                           8489 non-null   float64
 4   Net Income to Company (Rp.M)                  8489 non-null   float64
 5   target_distress_consecutive_loss              5160 non-null   Int64  
 6   tahun_distress_consecutive_loss               4169 non-null   Int64  
 7   target_distress_neg_equity                    6403 non-null   Int64  
 8   tahun_distress_neg_equity                     1073 non-null   Int64  
 9   target_distress_ppk                           2583 non-null   Int64  
 10  tahun_distress_ppk                            3421 non-null   Int64  
 11  1st Level Primary Industry                    8489 non-null   object 
 12  Year Established                              8468 non-null   Int64  
 13  Sector                                        8489 non-null   object 
 14  Industry Group                                8489 non-null   object 
 15  Parent Percent Owned (%)                      4848 non-null   float64
 16  Percent Owned - All Institutions (%)          4542 non-null   float64
 17  Percent Owned - Insiders (%)                  5938 non-null   float64
 18  Total Liabilities (Rp.M)                      8489 non-null   float64
 19  Total Assets (Rp.M)                           8489 non-null   float64
 20  Current Ratio (x)                             7783 non-null   float64
 21  Quick Ratio (x)                               7785 non-null   float64
 22  Working Capital (Rp.M)                        8489 non-null   float64
 23  Total Current Assets (Rp.M)                   8489 non-null   float64
 24  Total Current Liabilities (Rp.M)              8489 non-null   float64
 25  Inventory (Rp.M)                              7283 non-null   float64
 26  Prepaid Exp. (Rp.M)                           7027 non-null   float64
 27  Long-term Debt (Rp.M)                         5908 non-null   float64
 28  Short-term Borrowings (Rp.M)                  5579 non-null   float64
 29  Current Portion of LT Debt & Leases (Rp.M)    337 non-null    float64
 30  Total Debt (Rp.M)                             8468 non-null   float64
 31  Net Property, Plant & Equipment (Rp.M)        8470 non-null   float64
 32  Cost Of Goods Sold (Rp.M)                     8309 non-null   float64
 33  Total Revenue (Rp.M)                          8458 non-null   float64
 34  Operating Income (Rp.M)                       8488 non-null   float64
 35  EBITDA (Rp.M)                                 7383 non-null   float64
 36  EBIT (Rp.M)                                   7390 non-null   float64
 37  Cash from Ops. (Rp.M)                         8487 non-null   float64
 38  Net Change in Cash (Rp.M)                     8487 non-null   float64
 39  ECS Total Common Shares Outstanding (actual)  8488 non-null   float64
 40  Market Capitalization (Rp.M)                  8464 non-null   float64
 41  Year Close Stock Price (Rp.)                  8464 non-null   float64
 42  Total Capital (Rp.M)                          8468 non-null   float64
 43  Cash & Short-term Investments (Rp.M)          8489 non-null   float64
 44  Net Intangibles (Rp.M)                        1763 non-null   float64
 45  IPO Year                                      8489 non-null   Int64  
dtypes: Int64(9), float64(32), object(5)
memory usage: 3.1+ MB

Statistik Deskriptif

# STATISTIK DESKRIPTIF
# ---------------------------------------------------------------------
# Statistik akun keuangan + ukuran kemiringan (skewness) untuk melihat outlier/skew
desc = df[account_cols].describe().T
desc['skew'] = df[account_cols].skew()
print("Statistik deskriptif kolom akun keuangan:")
display(desc)

print("\nStatistik deskriptif kolom kepemilikan (%):")
display(df[ownership_cols].describe().T)
Statistik deskriptif kolom akun keuangan:
count mean std min 25% 50% 75% max skew
variable
Total Equity (Rp.M) 8489.000000 5533269.486397 18365513.535013 -87050020.759605 273118.240000 1041363.576000 3799002.091000 323189047.000000 9.005794
Net Income to Company (Rp.M) 8489.000000 636614.144596 3546958.614528 -90518726.000000 430.230000 42459.356000 301507.000000 61165121.000000 4.953522
Total Liabilities (Rp.M) 8489.000000 14365855.735244 83669335.745741 105.469000 250001.554000 1040515.029000 4483434.000000 2113748581.000000 13.740723
Total Assets (Rp.M) 8489.000000 19900305.166074 99115652.675446 64.531000 660311.259000 2342166.844000 8664310.151000 2427223262.000000 13.246400
Current Ratio (x) 7783.000000 4.063096 14.353216 0.000000 1.037000 1.586000 2.857000 276.365000 10.952540
Quick Ratio (x) 7785.000000 2.903037 13.437101 0.000000 0.434000 0.880000 1.742000 297.354000 12.758758
Working Capital (Rp.M) 8489.000000 -5792751.533121 58173059.258148 -1447507351.000000 -26159.942214 133714.513000 772744.000000 63082588.680000 -14.131973
Total Current Assets (Rp.M) 8489.000000 5552412.025094 19778922.730145 14.919000 257225.476000 949464.000000 3265410.243732 359147736.000000 10.001406
Total Current Liabilities (Rp.M) 8489.000000 11345163.558216 75324978.564407 1.110000 150305.093000 607793.174262 2543062.000000 1789344987.000000 13.654257
Inventory (Rp.M) 7283.000000 998161.513457 2866979.425961 0.000000 42322.937000 213830.125000 771717.868103 47739541.000000 8.077677
Prepaid Exp. (Rp.M) 7027.000000 106623.493042 514971.576669 0.028000 1174.321000 5660.100000 28173.587500 10906161.000000 9.480459
Long-term Debt (Rp.M) 5908.000000 2737886.565554 7386712.479530 5.000000 55077.386890 363538.872500 1838513.763017 109128071.000000 5.578868
Short-term Borrowings (Rp.M) 5579.000000 949371.219869 3102496.051113 0.253000 35626.251000 150000.000000 601172.524500 116140873.000000 13.437665
Current Portion of LT Debt & Leases (Rp.M) 337.000000 4794345.240881 13616851.799595 9.000000 101826.000000 981859.000000 4294254.000000 125043760.000000 5.908768
Total Debt (Rp.M) 8468.000000 3305024.447430 10654043.662490 0.000000 42059.531000 348717.826000 1879074.686445 298631967.000000 9.208874
Net Property, Plant & Equipment (Rp.M) 8470.000000 3216671.766077 10052754.873905 0.000000 95125.648500 456350.858500 2023958.363500 207476000.000000 8.534967
Cost Of Goods Sold (Rp.M) 8309.000000 3667029.770752 10817107.827832 -76682.746564 103930.792000 625488.781000 2637254.308000 242444000.000000 9.336591
Total Revenue (Rp.M) 8458.000000 5632032.500394 15745437.066905 -10991269.000000 247222.537750 1122399.420500 4206282.250000 330920000.000000 8.205025
Operating Income (Rp.M) 8488.000000 937616.844427 3934202.484944 -27748197.047589 4897.785075 81820.979500 455310.152481 76194365.000000 9.010808
EBITDA (Rp.M) 7383.000000 1104429.396194 4236552.760944 -16905265.101372 18402.918000 143412.780010 630543.853500 88404432.185000 10.105122
EBIT (Rp.M) 7390.000000 756315.294702 3026788.571486 -27748197.047589 4090.300000 78170.976573 430061.987958 63300052.352331 8.750251
Cash from Ops. (Rp.M) 8487.000000 193650.044304 6030080.153494 -208081950.000000 -6481.723000 44403.802000 364037.821000 73354000.000000 -9.426745
Net Change in Cash (Rp.M) 8487.000000 124838.588478 3250552.461486 -65116261.000000 -33438.422987 700.148000 64222.911500 114267567.000000 9.234775
ECS Total Common Shares Outstanding (actual) 8488.000000 9892981510.426485 24765778836.124523 283500.000000 1137579698.000000 3415271200.000000 9696291166.000000 1070558449408.000000 20.680081
Market Capitalization (Rp.M) 8464.000000 34619318.249208 1062974538.590965 5549.999700 305968.304052 1246700.197998 5292261.822750 50060622505.000000 45.942635
Year Close Stock Price (Rp.) 8464.000000 8379.534973 188130.954536 2.000000 130.000000 346.342933 1000.000000 5000000.000000 26.491285
Total Capital (Rp.M) 8468.000000 8851445.762053 27309273.207565 -1401798.980000 468295.568750 1610557.023196 5967422.430000 612106648.000000 9.316133
Cash & Short-term Investments (Rp.M) 8489.000000 2498566.689343 11510112.520983 2.092000 27901.282000 169876.793000 920131.000000 271881498.000000 11.131815
Net Intangibles (Rp.M) 1763.000000 927546.615175 6573842.548758 0.197000 1497.722000 11922.000000 92345.264500 123032740.000000 12.089476

Statistik deskriptif kolom kepemilikan (%):
count mean std min 25% 50% 75% max
variable
Parent Percent Owned (%) 4848.000000 66.363779 17.788215 1.010000 54.892500 65.140000 80.000000 100.000000
Percent Owned - All Institutions (%) 4542.000000 8.113311 12.933426 0.000000 0.460000 4.380000 10.570000 99.350000
Percent Owned - Insiders (%) 5938.000000 13.472383 21.880073 0.000000 0.090000 2.800000 15.720000 92.500000

Analisis Missing Value

# ANALISIS NILAI KOSONG (MISSING VALUE)
# ---------------------------------------------------------------------
null_summary = pd.DataFrame({
    'Null Count': df.isnull().sum(),
    'Null %': (df.isnull().mean() * 100).round(2)
}).sort_values('Null %', ascending=False)
print("Ringkasan nilai kosong per kolom:")
display(null_summary)

# Visualisasi hanya untuk kolom yang punya nilai kosong
nz = null_summary[null_summary['Null Count'] > 0].reset_index(names='Kolom')
# nz = nz.rename(columns={'index': 'Kolom'})
plt.figure(figsize=(10, max(4, 0.32 * len(nz))))
sns.barplot(x='Null %', y='Kolom', data=nz, color='steelblue')
plt.title('Persentase Nilai Kosong per Kolom (df_findat_integrated)')
plt.xlabel('Persentase Kosong (%)')
plt.tight_layout()
plt.show()
Ringkasan nilai kosong per kolom:
Null Count Null %
variable
Current Portion of LT Debt & Leases (Rp.M) 8152 96.030000
tahun_distress_neg_equity 7416 87.360000
Net Intangibles (Rp.M) 6726 79.230000
target_distress_ppk 5906 69.570000
tahun_distress_ppk 5068 59.700000
tahun_distress_consecutive_loss 4320 50.890000
Percent Owned - All Institutions (%) 3947 46.500000
Parent Percent Owned (%) 3641 42.890000
target_distress_consecutive_loss 3329 39.220000
Short-term Borrowings (Rp.M) 2910 34.280000
Long-term Debt (Rp.M) 2581 30.400000
Percent Owned - Insiders (%) 2551 30.050000
target_distress_neg_equity 2086 24.570000
Prepaid Exp. (Rp.M) 1462 17.220000
Inventory (Rp.M) 1206 14.210000
EBITDA (Rp.M) 1106 13.030000
EBIT (Rp.M) 1099 12.950000
Current Ratio (x) 706 8.320000
Quick Ratio (x) 704 8.290000
Cost Of Goods Sold (Rp.M) 180 2.120000
Total Revenue (Rp.M) 31 0.370000
Year Close Stock Price (Rp.) 25 0.290000
Market Capitalization (Rp.M) 25 0.290000
Total Debt (Rp.M) 21 0.250000
Total Capital (Rp.M) 21 0.250000
Year Established 21 0.250000
Net Property, Plant & Equipment (Rp.M) 19 0.220000
Net Change in Cash (Rp.M) 2 0.020000
Cash from Ops. (Rp.M) 2 0.020000
Operating Income (Rp.M) 1 0.010000
ECS Total Common Shares Outstanding (actual) 1 0.010000
Sector 0 0.000000
Total Equity (Rp.M) 0 0.000000
1st Level Primary Industry 0 0.000000
Year 0 0.000000
Entity Name 0 0.000000
Net Income to Company (Rp.M) 0 0.000000
ticker 0 0.000000
Total Current Assets (Rp.M) 0 0.000000
Working Capital (Rp.M) 0 0.000000
Total Current Liabilities (Rp.M) 0 0.000000
Total Liabilities (Rp.M) 0 0.000000
Total Assets (Rp.M) 0 0.000000
Industry Group 0 0.000000
Cash & Short-term Investments (Rp.M) 0 0.000000
IPO Year 0 0.000000

# Kolom fitur numerik (sesuaikan bila perlu)
non_feature = {
    'ticker', 'Year', 'Entity Name', 'Industry Group',
    'target_distress_ppk', 'tahun_distress_ppk',
    'target_distress_neg_equity', 'tahun_distress_neg_equity',
    'target_distress_consecutive_loss', 'tahun_distress_consecutive_loss'
}
num_feature_cols = [c for c in df.columns
                    if c not in non_feature
                    and pd.api.types.is_numeric_dtype(df[c])
                    and not c.startswith('industry_')]

# Hanya fitur dengan null cukup berarti, agar heatmap ringkas
null_share = df[num_feature_cols].isnull().mean()
features = null_share[null_share > 0.002].sort_values(ascending=False).index.tolist()

# --- Heatmap 1: persentase null tiap fitur di tiap sektor ---
heat_sector = pd.DataFrame({
    c: df.groupby('Industry Group')[c].apply(lambda s: s.isnull().mean() * 100)
    for c in features
}).T  # baris = fitur, kolom = sektor

plt.figure(figsize=(14, 9))
sns.heatmap(heat_sector, cmap='Reds', linewidths=0.3,
            cbar_kws={'label': 'Persentase Nilai Kosong dalam Sektor (%)'})
plt.title('Persentase Nilai Kosong Tiap Fitur di Tiap Sektor')
plt.xlabel('Industry Group'); plt.ylabel('Fitur')
plt.tight_layout(); plt.show()

# --- Heatmap 2: persentase null tiap fitur per tahun ---
heat_year = pd.DataFrame({
    c: df.groupby('Year')[c].apply(lambda s: s.isnull().mean() * 100)
    for c in features
}).T

plt.figure(figsize=(12, 9))
sns.heatmap(heat_year, cmap='Blues', linewidths=0.3,
            cbar_kws={'label': 'Persentase Nilai Kosong per Tahun (%)'})
plt.title('Persentase Nilai Kosong Tiap Fitur per Tahun Fiskal')
plt.xlabel('Tahun'); plt.ylabel('Fitur')
plt.tight_layout(); plt.show()

Distribusi Kolom Numerik

# DISTRIBUSI NILAI KOLOM NUMERIK (HISTOGRAM)
# ---------------------------------------------------------------------
# data keuangan umumnya sangat skewed & banyak outlier.
# Histogram skala asli memperlihatkan kemiringan; aktifkan log jika perlu.
dist_cols = account_cols + ownership_cols
ncols = 4
nrows = int(np.ceil(len(dist_cols) / ncols))
fig, axes = plt.subplots(nrows, ncols, figsize=(4 * ncols, 3 * nrows))
for ax, col in zip(axes.flatten(), dist_cols):
    data = df[col].dropna()
    ax.hist(data, bins=50, color='steelblue')
    ax.set_title(col, fontsize=8)
    ax.tick_params(labelsize=7)
for ax in axes.flatten()[len(dist_cols):]:
    ax.axis('off')
fig.suptitle('Distribusi Nilai Kolom Numerik', y=1.002, fontsize=12)
plt.tight_layout()
plt.show()

dist_cols = account_cols + ownership_cols
ncols = 4
nrows = int(np.ceil(len(dist_cols) / ncols))
fig, axes = plt.subplots(nrows, ncols, figsize=(4 * ncols, 3 * nrows))
for ax, col in zip(axes.flatten(), dist_cols):
    data = df[col].dropna()
    ax.boxplot(data, vert=False,
               patch_artist=True,
               boxprops=dict(facecolor='steelblue', color='steelblue'),
               medianprops=dict(color='black'))
    ax.set_title(col, fontsize=8)
    ax.tick_params(labelsize=7)
for ax in axes.flatten()[len(dist_cols):]:
    ax.axis('off')
fig.suptitle('Distribusi Nilai Kolom Numerik', y=1.002, fontsize=12)
plt.tight_layout()
plt.show()

Distribusi Kolom Kategorikal

# DISTRIBUSI KOLOM KATEGORIKAL (SEKTOR / INDUSTRI)
# ---------------------------------------------------------------------
for col in categorical_cols:
    vc = df[col].value_counts()
    print(f"\n=== {col} ({df[col].nunique()} kategori) ===")
    print(vc)
    plt.figure(figsize=(9, max(3, 0.32 * len(vc))))
    sns.barplot(x=vc.values, y=vc.index, color='teal')
    plt.title(f'Distribusi Jumlah Baris per {col}')
    plt.xlabel('Jumlah Baris')
    plt.tight_layout()
    plt.show()

=== 1st Level Primary Industry (8 kategori) ===
1st Level Primary Industry
Consumer                                  2210
Industrials                               1428
Financials                                1318
Materials                                 1019
Real Estate                                871
Energy and Utilities                       730
Technology, Media & Telecommunications     638
Health Care                                275
Name: count, dtype: int64


=== Sector (11 kategori) ===
Sector
Industrials               1445
Financials                1331
Consumer Discretionary    1136
Consumer Staples          1062
Materials                 1019
Real Estate                854
Energy                     665
Communication Services     462
Health Care                275
Information Technology     175
Utilities                   65
Name: count, dtype: int64


=== Industry Group (23 kategori) ===
Industry Group
Materials                                           1019
Real Estate Management and Development               854
Food, Beverage and Tobacco                           802
Capital Goods                                        769
Energy                                               665
Banks                                                635
Transportation                                       544
Financial Services                                   419
Consumer Services                                    360
Consumer Durables and Apparel                        323
Consumer Discretionary Distribution and Retail       322
Insurance                                            277
Media and Entertainment                              254
Telecommunication Services                           208
Consumer Staples Distribution and Retail             159
Health Care Equipment and Services                   146
Commercial and Professional Services                 132
Automobiles and Components                           131
Pharmaceuticals, Biotechnology and Life Sciences     129
Household and Personal Products                      101
Technology Hardware and Equipment                     93
Software and Services                                 82
Utilities                                             65
Name: count, dtype: int64

Riwayat Tahun Data Perusahaan

# CAKUPAN TEMPORAL PERUSAHAAN
# ---------------------------------------------------------------------
comp_per_year = df.groupby('Year')['ticker'].nunique()
print("Jumlah perusahaan unik per tahun:")
print(comp_per_year)
comp_per_year.plot(kind='bar', figsize=(10, 4), color='steelblue')
plt.title('Jumlah Perusahaan Unik per Tahun Fiskal')
plt.ylabel('Jumlah Perusahaan')
plt.tight_layout()
plt.show()

years_per_comp = df.groupby('ticker')['Year'].nunique()
print("\nDistribusi jumlah tahun fiskal per perusahaan:")
print(years_per_comp.describe())
Jumlah perusahaan unik per tahun:
Year
2008    302
2009    315
2010    325
2011    344
2012    367
2013    392
2014    417
2015    441
2016    460
2017    475
2018    508
2019    559
2020    611
2021    661
2022    715
2023    765
2024    832
Name: ticker, dtype: int64


Distribusi jumlah tahun fiskal per perusahaan:
count   853.000000
mean      9.951934
std       6.263298
min       1.000000
25%       4.000000
50%      10.000000
75%      17.000000
max      17.000000
Name: Year, dtype: float64

Analisis Target : Tingkat Distress Per Sektor & Per Tahun

# ANALISIS TARGET: TINGKAT DISTRESS PER SEKTOR & PER WAKTU
# ---------------------------------------------------------------------
for target in target_cols:
    sub = df[df[target].notna()].copy()
    n_pos = int(sub[target].sum())
    print(f"\n########## {target} ##########")
    print(f"Total berlabel: {len(sub)} | Distress: {n_pos} "
          f"({sub[target].mean() * 100:.2f}%)")

    # Tingkat distress per Industry Group (sektor dengan minimal 20 baris)
    grp = sub.groupby('Industry Group')[target].agg(['mean', 'sum', 'count'])
    grp = grp[grp['count'] >= 20].sort_values('mean', ascending=False)
    grp['mean'] = (grp['mean'] * 100).round(2)
    grp = grp.rename(columns={'mean': 'Distress %',
                              'sum': 'Jumlah Distress',
                              'count': 'Jumlah Baris'})
    print("\nTingkat distress per Industry Group (min. 20 baris):")
    display(grp)

    if len(grp) > 0:
        plt.figure(figsize=(9, max(3, 0.32 * len(grp))))
        sns.barplot(x='Distress %', y=grp.index, data=grp.reset_index(),
                    color='indianred')
        plt.title(f'Tingkat Distress per Sektor - {target}')
        plt.tight_layout()
        plt.show()

    # Tingkat distress per tahun fiskal
    rate_year = sub.groupby('Year')[target].mean() * 100
    rate_year.plot(marker='o', figsize=(8, 4), color='indianred')
    plt.ylim(ymin=0)
    plt.title(f'Tingkat Distress per Tahun Fiskal - {target}')
    plt.ylabel('Distress %')
    plt.tight_layout()
    plt.show()

########## target_distress_ppk ##########
Total berlabel: 2583 | Distress: 510 (19.74%)

Tingkat distress per Industry Group (min. 20 baris):
Distress % Jumlah Distress Jumlah Baris
Industry Group
Consumer Services 33.330000 37 111
Consumer Durables and Apparel 28.720000 27 94
Media and Entertainment 26.970000 24 89
Real Estate Management and Development 25.190000 68 270
Capital Goods 22.880000 54 236
Commercial and Professional Services 22.860000 8 35
Transportation 22.030000 39 177
Materials 20.410000 60 294
Energy 20.110000 37 184
Software and Services 19.440000 7 36
Financial Services 18.350000 20 109
Telecommunication Services 18.030000 11 61
Consumer Discretionary Distribution and Retail 17.480000 18 103
Food, Beverage and Tobacco 15.950000 41 257
Consumer Staples Distribution and Retail 15.560000 7 45
Insurance 14.630000 12 82
Technology Hardware and Equipment 13.890000 5 36
Utilities 12.500000 3 24
Health Care Equipment and Services 12.070000 7 58
Pharmaceuticals, Biotechnology and Life Sciences 11.760000 4 34
Automobiles and Components 11.760000 4 34
Household and Personal Products 8.570000 3 35
Banks 7.820000 14 179


########## target_distress_neg_equity ##########
Total berlabel: 6403 | Distress: 116 (1.81%)

Tingkat distress per Industry Group (min. 20 baris):
Distress % Jumlah Distress Jumlah Baris
Industry Group
Transportation 5.370000 18 335
Technology Hardware and Equipment 5.080000 3 59
Utilities 4.440000 2 45
Energy 3.790000 18 475
Consumer Durables and Apparel 3.700000 7 189
Media and Entertainment 3.630000 7 193
Consumer Discretionary Distribution and Retail 2.840000 6 211
Capital Goods 2.830000 16 566
Telecommunication Services 2.350000 4 170
Consumer Services 2.140000 6 281
Health Care Equipment and Services 2.080000 2 96
Pharmaceuticals, Biotechnology and Life Sciences 1.820000 2 110
Consumer Staples Distribution and Retail 1.560000 2 128
Materials 1.490000 12 808
Food, Beverage and Tobacco 1.150000 7 610
Real Estate Management and Development 0.440000 3 682
Banks 0.200000 1 510
Commercial and Professional Services 0.000000 0 106
Automobiles and Components 0.000000 0 111
Insurance 0.000000 0 235
Household and Personal Products 0.000000 0 78
Financial Services 0.000000 0 355
Software and Services 0.000000 0 50


########## target_distress_consecutive_loss ##########
Total berlabel: 5160 | Distress: 565 (10.95%)

Tingkat distress per Industry Group (min. 20 baris):
Distress % Jumlah Distress Jumlah Baris
Industry Group
Software and Services 23.810000 10 42
Consumer Services 21.430000 45 210
Transportation 19.830000 46 232
Consumer Durables and Apparel 17.280000 28 162
Media and Entertainment 16.780000 24 143
Energy 15.020000 50 333
Capital Goods 12.900000 60 465
Utilities 12.500000 5 40
Consumer Discretionary Distribution and Retail 12.220000 22 180
Technology Hardware and Equipment 11.900000 5 42
Real Estate Management and Development 11.520000 60 521
Materials 11.180000 69 617
Consumer Staples Distribution and Retail 10.780000 11 102
Health Care Equipment and Services 10.390000 8 77
Telecommunication Services 9.380000 12 128
Household and Personal Products 9.230000 6 65
Food, Beverage and Tobacco 8.020000 41 511
Financial Services 7.220000 21 291
Commercial and Professional Services 6.520000 6 92
Automobiles and Components 6.000000 6 100
Banks 4.870000 23 472
Pharmaceuticals, Biotechnology and Life Sciences 4.760000 5 105
Insurance 0.870000 2 230

n_targets = len(target_cols)

# 1. MENENTUKAN JANGKAR URUTAN SECARA ALFABETIS (MASTER ORDER)
target_acuan = target_cols[0]
sub_acuan = df[df[target_acuan].notna()].copy()

grp_acuan = sub_acuan.groupby('Industry Group')[target_acuan].agg(['mean', 'count'])

# Saring minimal 20 baris, lalu urutkan nama sektornya secara ALFABETIS.
# ascending=False digunakan agar abjad A dirender di posisi paling atas layar.
grp_acuan = grp_acuan[grp_acuan['count'] >= 20].sort_index(ascending=False)

master_order = grp_acuan.index.tolist()

# 2. INISIALISASI KANVAS UTAMA
fig, axes = plt.subplots(nrows=1, ncols=n_targets, figsize=(5 * n_targets, 12), sharey=True)

if n_targets == 1:
    axes = [axes]

for i, target in enumerate(target_cols):
    ax = axes[i]
    
    sub = df[df[target].notna()].copy()
    grp = sub.groupby('Industry Group')[target].agg(['mean', 'sum', 'count'])
    
    # Kunci urutan baris mengikuti jangkar alfabetis
    grp = grp.reindex(master_order).fillna(0)

    plot_df = pd.DataFrame(index=grp.index)
    plot_df['Aman (Target: 0)'] = grp['count'] - grp['sum']
    plot_df['Distress (Target: 1)'] = grp['sum']

    plot_df.plot(
        kind='barh', 
        stacked=True, 
        color=['#A8E6CF', '#FF8A80'], 
        width=0.8,
        ax=ax
    )

    # Anotasi Teks HANYA di bar merah
    for n, (index, row) in enumerate(plot_df.iterrows()):
        sehat_count = row['Aman (Target: 0)']
        distress_count = row['Distress (Target: 1)']
        total_bar_count = grp.loc[index, 'count']
        
        if distress_count > 0 and total_bar_count > 0:
            distress_pct = (distress_count / total_bar_count) * 100
            
            x_pos = sehat_count + (distress_count / 2)
            teks_anotasi = f"{int(distress_count)}/{int(total_bar_count)} ({distress_pct:.1f}%)"
            
            ax.text(x_pos, n, teks_anotasi, va='center', ha='center', color='black', fontsize=9)

    ax.set_title(f"Target:\n{target}", pad=15, fontweight='bold')
    ax.set_xlabel("Jumlah Baris Data")
    ax.set_ylabel("") 
    
    # 3. MENGHAPUS SEMUA LEGENDA INTERNAL
    if ax.get_legend():
        ax.get_legend().remove()
        
    if i > 0:
        ax.tick_params(axis='y', which='both', length=0)

# 4. MEMBUAT LEGENDA GLOBAL DI TENGAH BAWAH
handles, labels = axes[0].get_legend_handles_labels()
fig.legend(handles, labels, loc='lower center', bbox_to_anchor=(0.5, 0.01), ncol=2, fontsize=11)

# 5. MERAPIKAN JARAK DAN MEMBERI RUANG BAWAH
plt.tight_layout(w_pad=0.5, rect=[0, 0.05, 1, 1])
plt.show()

import matplotlib.pyplot as plt
import pandas as pd

n_targets = len(target_cols)

# 1. MENENTUKAN JANGKAR URUTAN (MASTER ORDER)
# Gunakan target pertama di dalam list sebagai acuan urutan sektor.
target_acuan = target_cols[0]
sub_acuan = df[df[target_acuan].notna()].copy()

grp_acuan = sub_acuan.groupby('Industry Group')[target_acuan].agg(['mean', 'count'])
grp_acuan = grp_acuan[grp_acuan['count'] >= 20].sort_values('mean', ascending=True)

# Kunci urutan nama sektor industri ke dalam satu list paten
master_order = grp_acuan.index.tolist()

# 2. INISIALISASI KANVAS DENGAN SUMBU Y BERSAMA (sharey=True)
# Lebar kanvas dikurangi sedikit (5 inci per grafik) karena ruang nama sektor sudah hemat
fig, axes = plt.subplots(nrows=1, ncols=n_targets, figsize=(5 * n_targets, 12), sharey=True)

if n_targets == 1:
    axes = [axes]

for i, target in enumerate(target_cols):
    ax = axes[i]
    
    sub = df[df[target].notna()].copy()
    
    # Agregasi data
    grp = sub.groupby('Industry Group')[target].agg(['mean', 'sum', 'count'])
    
    # 3. KUNCI STRUKTUR BARIS MENGGUNAKAN MASTER ORDER
    # Jika ada sektor di target lain yang tidak punya data, isi dengan 0 (fillna)
    grp = grp.reindex(master_order).fillna(0)

    # Siapkan DataFrame khusus plotting
    plot_df = pd.DataFrame(index=grp.index)
    plot_df['Aman (Target: 0)'] = grp['count'] - grp['sum']
    plot_df['Distress (Target: 1)'] = grp['sum']

    # Plotting
    plot_df.plot(
        kind='barh', 
        stacked=True, 
        color=['#A8E6CF', '#FF8A80'], 
        width=0.8,
        ax=ax
    )

    # Anotasi Teks HANYA di bar merah
    for n, (index, row) in enumerate(plot_df.iterrows()):
        sehat_count = row['Aman (Target: 0)']
        distress_count = row['Distress (Target: 1)']
        total_bar_count = grp.loc[index, 'count']
        
        # Validasi tambahan: total bar tidak boleh 0 untuk mencegah error pembagian (ZeroDivisionError)
        if distress_count > 0 and total_bar_count > 0:
            distress_pct = (distress_count / total_bar_count) * 100
            
            x_pos = sehat_count + (distress_count / 2)
            teks_anotasi = f"{int(distress_count)}/{int(total_bar_count)} ({distress_pct:.1f}%)"
            
            ax.text(x_pos, n, teks_anotasi, va='center', ha='center', color='black', fontsize=9)

    # Konfigurasi Tampilan
    ax.set_title(f"Target:\n{target}", pad=15, fontweight='bold')
    ax.set_xlabel("Jumlah Baris Data")
    ax.set_ylabel("") 
    
    # Manajemen Legenda dan Garis Sumbu
    if i == 0:
        ax.legend(loc='lower right')
    else:
        # Hapus legenda untuk grafik ke-2, ke-3, dst.
        ax.get_legend().remove()
        
        # Hilangkan juga garis tick kecil-kecil di sumbu Y pada grafik sebelah kanan agar mulus
        ax.tick_params(axis='y', which='both', length=0)

# 4. MERAPIKAN JARAK ANTAR GRAFIK
# Menggunakan w_pad (width padding) yang sangat kecil agar antar grafik seolah menempel
plt.tight_layout(w_pad=0.5)
plt.show()

Korelasi Antar Kolom Keuangan

# KORELASI ANTAR KOLOM AKUN KEUANGAN
# ---------------------------------------------------------------------
# Spearman dipakai karena hubungan antar akun keuangan kerap non-linear &
# banyak outlier. Korelasi tinggi antar akun mentah lazim terjadi karena
# nilainya sama-sama meningkat seiring ukuran perusahaan -- ini sekaligus
# menjadi alasan transformasi ke rasio keuangan pada tahap persiapan data.
corr = df[account_cols].corr(method='spearman')
plt.figure(figsize=(14, 12))
sns.heatmap(corr, cmap='coolwarm', center=0, square=True,
            cbar_kws={'shrink': 0.6})
plt.title('Heatmap Korelasi Spearman Antar Kolom Akun Keuangan')
plt.tight_layout()
plt.show()

# Pasangan kolom dengan korelasi absolut tinggi
mask = np.triu(np.ones(corr.shape), k=1).astype(bool)
high_pairs = corr.abs().where(mask).stack().sort_values(ascending=False)
print("Pasangan kolom dengan |korelasi Spearman| > 0.8:")
print(high_pairs[high_pairs > 0.8])

Pasangan kolom dengan |korelasi Spearman| > 0.8:
variable                                    variable                            
Operating Income (Rp.M)                     EBIT (Rp.M)                            1.000000
Total Assets (Rp.M)                         Total Capital (Rp.M)                   0.969918
Total Liabilities (Rp.M)                    Total Current Liabilities (Rp.M)       0.955142
                                            Total Assets (Rp.M)                    0.943063
Total Equity (Rp.M)                         Total Capital (Rp.M)                   0.937788
Operating Income (Rp.M)                     EBITDA (Rp.M)                          0.931527
EBITDA (Rp.M)                               EBIT (Rp.M)                            0.931527
Total Assets (Rp.M)                         Total Current Assets (Rp.M)            0.924849
Long-term Debt (Rp.M)                       Total Debt (Rp.M)                      0.923254
Cost Of Goods Sold (Rp.M)                   Total Revenue (Rp.M)                   0.910787
Total Equity (Rp.M)                         Total Assets (Rp.M)                    0.910582
Total Assets (Rp.M)                         Total Current Liabilities (Rp.M)       0.900747
Total Current Assets (Rp.M)                 Total Capital (Rp.M)                   0.889587
Total Liabilities (Rp.M)                    Total Current Assets (Rp.M)            0.881772
Total Current Assets (Rp.M)                 Total Current Liabilities (Rp.M)       0.881733
Total Liabilities (Rp.M)                    Total Capital (Rp.M)                   0.879436
Net Income to Company (Rp.M)                Operating Income (Rp.M)                0.870587
Total Current Assets (Rp.M)                 Cash & Short-term Investments (Rp.M)   0.866649
Net Income to Company (Rp.M)                EBIT (Rp.M)                            0.854423
Total Liabilities (Rp.M)                    Long-term Debt (Rp.M)                  0.851622
Total Equity (Rp.M)                         Total Current Assets (Rp.M)            0.846800
                                            Market Capitalization (Rp.M)           0.844694
Total Assets (Rp.M)                         Cash & Short-term Investments (Rp.M)   0.838243
Market Capitalization (Rp.M)                Total Capital (Rp.M)                   0.834973
Total Assets (Rp.M)                         Market Capitalization (Rp.M)           0.833850
Long-term Debt (Rp.M)                       Total Capital (Rp.M)                   0.833134
Current Ratio (x)                           Quick Ratio (x)                        0.821145
Total Current Assets (Rp.M)                 Total Revenue (Rp.M)                   0.820885
Total Current Liabilities (Rp.M)            Total Capital (Rp.M)                   0.818591
Total Assets (Rp.M)                         Long-term Debt (Rp.M)                  0.818238
Total Revenue (Rp.M)                        EBITDA (Rp.M)                          0.817761
Total Equity (Rp.M)                         Cash & Short-term Investments (Rp.M)   0.814297
Total Liabilities (Rp.M)                    Total Debt (Rp.M)                      0.813864
Net Property, Plant & Equipment (Rp.M)      Total Capital (Rp.M)                   0.806563
Current Portion of LT Debt & Leases (Rp.M)  Total Debt (Rp.M)                      0.800969
dtype: float64

Analisis MNAR Kolom Raw

# # %% DETEKSI MEKANISME NILAI KOSONG (informative missingness)
# import pandas as pd, numpy as np
# from scipy.stats import chi2_contingency

df = df_findat_integrated.copy()

# Kolom fitur numerik yang memiliki nilai kosong (di luar identitas/target/sektor)
non_feature = {
    'ticker', 'Year', 'Entity Name', '1st Level Primary Industry', 'Sector',
    'Industry Group', 'Date Established', 'Year Established', 'IPO Year',
    'target_distress_ppk', 'target_distress_neg_equity',
    'target_distress_consecutive_loss', 'tahun_distress_ppk',
    'tahun_distress_neg_equity', 'tahun_distress_consecutive_loss'
}
feature_cols = [c for c in df.columns
                if c not in non_feature
                and pd.api.types.is_numeric_dtype(df[c])
                and df[c].isnull().any()]

target_cols = ['target_distress_ppk',
               'target_distress_neg_equity',
               'target_distress_consecutive_loss']

# 1) ASOSIASI MISSINGNESS DENGAN TARGET
# Bandingkan tingkat distress saat suatu fitur kosong vs terisi, lalu uji p value

rows = []
for target in target_cols:
    sub = df[df[target].notna()]
    for col in feature_cols:
        is_null = sub[col].isnull()
        if is_null.sum() < 10 or (~is_null).sum() < 10:
            continue
        ct = pd.crosstab(is_null, sub[target])
        if ct.shape != (2, 2):
            continue
        _, p = fisher_exact(ct)   # sebelumnya: _, p, _, _ = chi2_contingency(ct)
        rows.append({
            'target': target, 'fitur': col,
            'distress_saat_kosong_%': round(sub.loc[is_null, target].mean() * 100, 2),
            'distress_saat_terisi_%': round(sub.loc[~is_null, target].mean() * 100, 2),
            'p_value': p
        })

assoc = pd.DataFrame(rows)
assoc['signifikan_(p<0.05)'] = assoc['p_value'] < 0.05
assoc = assoc.sort_values(['target', 'p_value'])

# rows = []
# for target in target_cols:
#     sub = df[df[target].notna()]
#     for col in feature_cols:
#         is_null = sub[col].isnull()
#         if is_null.sum() < 10 or (~is_null).sum() < 10:
#             continue  # lewati bila salah satu kelompok terlalu sedikit
#         ct = pd.crosstab(is_null, sub[target])
#         try:
#             _, p, _, _ = chi2_contingency(ct)
#         except Exception:
#             p = np.nan
#         rows.append({
#             'target': target,
#             'fitur': col,
#             'distress_saat_kosong_%': round(sub.loc[is_null, target].mean() * 100, 2),
#             'distress_saat_terisi_%': round(sub.loc[~is_null, target].mean() * 100, 2),
#             'p_value': p
#         })



# assoc = pd.DataFrame(rows)
# assoc['signifikan_(p<0.05)'] = assoc['p_value'] < 0.05
# assoc = assoc.sort_values(['target', 'p_value'])
# print("Keterkaitan ketiadaan data dengan target distress:")

display(assoc)

# 2) KO-OKUR    ENSI MISSINGNESS (apakah kolom cenderung kosong bersamaan)
miss = df[feature_cols].isnull().astype(int)
corr_miss = miss.corr()
mask = np.triu(np.ones(corr_miss.shape), k=1).astype(bool)
pairs = corr_miss.where(mask).stack().sort_values(ascending=False)
print("\nPasangan indikator ketiadaan data dengan korelasi tertinggi:")
print(pairs[pairs > 0.3].head(20))
target fitur distress_saat_kosong_% distress_saat_terisi_% p_value signifikan_(p<0.05)
35 target_distress_consecutive_loss Percent Owned - All Institutions (%) 16.140000 7.440000 0.000000 True
34 target_distress_consecutive_loss Parent Percent Owned (%) 14.800000 8.550000 0.000000 True
38 target_distress_consecutive_loss Quick Ratio (x) 4.830000 11.630000 0.000000 True
37 target_distress_consecutive_loss Current Ratio (x) 4.810000 11.640000 0.000000 True
46 target_distress_consecutive_loss EBITDA (Rp.M) 6.370000 11.770000 0.000003 True
47 target_distress_consecutive_loss EBIT (Rp.M) 6.370000 11.770000 0.000003 True
43 target_distress_consecutive_loss Current Portion of LT Debt & Leases (Rp.M) 11.320000 3.610000 0.000027 True
39 target_distress_consecutive_loss Inventory (Rp.M) 7.840000 11.500000 0.002222 True
40 target_distress_consecutive_loss Prepaid Exp. (Rp.M) 13.600000 10.460000 0.009990 True
41 target_distress_consecutive_loss Long-term Debt (Rp.M) 9.740000 11.480000 0.072464 False
48 target_distress_consecutive_loss Market Capitalization (Rp.M) 23.530000 10.910000 0.106952 False
49 target_distress_consecutive_loss Year Close Stock Price (Rp.) 23.530000 10.910000 0.106952 False
36 target_distress_consecutive_loss Percent Owned - Insiders (%) 12.010000 10.500000 0.118931 False
51 target_distress_consecutive_loss Net Intangibles (Rp.M) 10.740000 12.110000 0.265771 False
42 target_distress_consecutive_loss Short-term Borrowings (Rp.M) 10.440000 11.200000 0.421441 False
44 target_distress_consecutive_loss Total Debt (Rp.M) 14.290000 10.940000 0.660313 False
50 target_distress_consecutive_loss Total Capital (Rp.M) 14.290000 10.940000 0.660313 False
45 target_distress_consecutive_loss Cost Of Goods Sold (Rp.M) 9.350000 10.980000 0.753745 False
15 target_distress_neg_equity Percent Owned - All Institutions (%) 2.850000 1.020000 0.000000 True
14 target_distress_neg_equity Parent Percent Owned (%) 2.880000 1.060000 0.000000 True
17 target_distress_neg_equity Current Ratio (x) 0.180000 1.970000 0.000409 True
18 target_distress_neg_equity Quick Ratio (x) 0.180000 1.970000 0.000409 True
30 target_distress_neg_equity Market Capitalization (Rp.M) 19.050000 1.750000 0.000482 True
31 target_distress_neg_equity Year Close Stock Price (Rp.) 19.050000 1.750000 0.000482 True
21 target_distress_neg_equity Long-term Debt (Rp.M) 0.980000 2.170000 0.000705 True
29 target_distress_neg_equity EBIT (Rp.M) 0.780000 1.980000 0.009799 True
28 target_distress_neg_equity EBITDA (Rp.M) 0.780000 1.980000 0.009856 True
23 target_distress_neg_equity Current Portion of LT Debt & Leases (Rp.M) 1.890000 0.000000 0.015199 True
22 target_distress_neg_equity Short-term Borrowings (Rp.M) 1.420000 2.010000 0.093650 False
33 target_distress_neg_equity Net Intangibles (Rp.M) 1.720000 2.270000 0.208212 False
27 target_distress_neg_equity Total Revenue (Rp.M) 7.690000 1.800000 0.211718 False
20 target_distress_neg_equity Prepaid Exp. (Rp.M) 2.250000 1.720000 0.217022 False
26 target_distress_neg_equity Cost Of Goods Sold (Rp.M) 2.800000 1.790000 0.331140 False
19 target_distress_neg_equity Inventory (Rp.M) 1.490000 1.870000 0.508083 False
16 target_distress_neg_equity Percent Owned - Insiders (%) 1.950000 1.750000 0.610674 False
24 target_distress_neg_equity Total Debt (Rp.M) 0.000000 1.820000 1.000000 False
25 target_distress_neg_equity Net Property, Plant & Equipment (Rp.M) 0.000000 1.820000 1.000000 False
32 target_distress_neg_equity Total Capital (Rp.M) 0.000000 1.820000 1.000000 False
1 target_distress_ppk Percent Owned - All Institutions (%) 26.170000 13.430000 0.000000 True
0 target_distress_ppk Parent Percent Owned (%) 24.630000 15.910000 0.000000 True
9 target_distress_ppk Current Portion of LT Debt & Leases (Rp.M) 20.440000 5.740000 0.000012 True
4 target_distress_ppk Quick Ratio (x) 9.760000 20.610000 0.000077 True
3 target_distress_ppk Current Ratio (x) 10.140000 20.580000 0.000170 True
2 target_distress_ppk Percent Owned - Insiders (%) 24.530000 17.970000 0.000288 True
6 target_distress_ppk Prepaid Exp. (Rp.M) 25.500000 18.540000 0.001064 True
13 target_distress_ppk Net Intangibles (Rp.M) 21.660000 16.730000 0.002328 True
10 target_distress_ppk Cost Of Goods Sold (Rp.M) 4.080000 20.050000 0.003105 True
11 target_distress_ppk EBITDA (Rp.M) 14.090000 20.460000 0.009757 True
12 target_distress_ppk EBIT (Rp.M) 14.240000 20.440000 0.011911 True
7 target_distress_ppk Long-term Debt (Rp.M) 21.540000 18.950000 0.133723 False
8 target_distress_ppk Short-term Borrowings (Rp.M) 21.210000 18.880000 0.152223 False
5 target_distress_ppk Inventory (Rp.M) 20.450000 19.630000 0.720486 False

Pasangan indikator ketiadaan data dengan korelasi tertinggi:
variable                      variable                    
Cash from Ops. (Rp.M)         Net Change in Cash (Rp.M)      1.000000
Total Debt (Rp.M)             Total Capital (Rp.M)           1.000000
Market Capitalization (Rp.M)  Year Close Stock Price (Rp.)   1.000000
Current Ratio (x)             Quick Ratio (x)                0.998454
EBITDA (Rp.M)                 EBIT (Rp.M)                    0.996358
Quick Ratio (x)               EBIT (Rp.M)                    0.761981
Current Ratio (x)             EBIT (Rp.M)                    0.760670
Quick Ratio (x)               EBITDA (Rp.M)                  0.759190
Current Ratio (x)             EBITDA (Rp.M)                  0.757881
Inventory (Rp.M)              EBITDA (Rp.M)                  0.350709
                              EBIT (Rp.M)                    0.345621
                              Prepaid Exp. (Rp.M)            0.318408
dtype: float64
# === Visualisasi 1: selisih proporsi distress (kosong - terisi) per fitur ===
# 'assoc' adalah dataframe hasil uji keterkaitan yang sudah di peroleh
assoc = assoc.copy()
assoc['selisih'] = assoc['distress_saat_kosong_%'] - assoc['distress_saat_terisi_%']

targets = ['target_distress_ppk', 'target_distress_neg_equity', 'target_distress_consecutive_loss']
judul = ['Papan Pemantauan Khusus', 'Ekuitas Negatif', 'Kerugian Beruntun']

fig, axes = plt.subplots(1, 3, figsize=(18, 8))
for ax, tgt, jdl in zip(axes, targets, judul):
    sub = assoc[assoc['target'] == tgt].sort_values('selisih')
    warna = ['#c0392b' if p < 0.05 else '#bdc3c7' for p in sub['p_value']]
    ax.barh(sub['fitur'], sub['selisih'], color=warna)
    ax.axvline(0, color='black', linewidth=0.8)
    ax.set_title(jdl, fontsize=11)
    ax.set_xlabel('% Selisih Proporsi Distress (Kosong − Terisi)')
    ax.tick_params(axis='y', labelsize=8)
fig.legend(handles=[Patch(color='#c0392b', label='Signifikan (p < 0,05)'),
                    Patch(color='#bdc3c7', label='Tidak signifikan')],
           loc='upper right')
plt.suptitle('Selisih Proporsi Distress antara Baris Kosong dan Terisi per Fitur', fontsize=13)
plt.tight_layout()
plt.show()

# === Visualisasi 2: heatmap kecenderungan kolom kosong secara bersamaan ===
non_feature = {
    'ticker', 'Year', 'Entity Name', '1st Level Primary Industry', 'Sector',
    'Industry Group', 'Date Established', 'Year Established', 'IPO Year',
    'target_distress_ppk', 'target_distress_neg_equity',
    'target_distress_consecutive_loss', 'tahun_distress_ppk',
    'tahun_distress_neg_equity', 'tahun_distress_consecutive_loss'
}
feat = [c for c in df_findat_integrated.columns
        if c not in non_feature
        and pd.api.types.is_numeric_dtype(df_findat_integrated[c])
        and df_findat_integrated[c].isnull().any()]

miss = df_findat_integrated[feat].isnull().astype(int)
corr_miss = miss.corr()

plt.figure(figsize=(12, 10))
sns.heatmap(corr_miss, cmap='coolwarm', center=0, square=True, linewidths=0.3,
            cbar_kws={'label': 'Korelasi Pola Kosong'})
plt.title('Heatmap Kecenderungan Kolom Kosong Secara Bersamaan')
plt.tight_layout()
plt.show()

Verifikasi Kualitias Data

DatPrep : Feature Engineering

Pilih Fitur Sektor/Industri

df_findat_integrated
variable ticker Year Entity Name Total Equity (Rp.M) Net Income to Company (Rp.M) target_distress_consecutive_loss tahun_distress_consecutive_loss target_distress_neg_equity tahun_distress_neg_equity target_distress_ppk tahun_distress_ppk 1st Level Primary Industry Year Established Sector Industry Group Parent Percent Owned (%) Percent Owned - All Institutions (%) Percent Owned - Insiders (%) Total Liabilities (Rp.M) Total Assets (Rp.M) Current Ratio (x) Quick Ratio (x) Working Capital (Rp.M) Total Current Assets (Rp.M) Total Current Liabilities (Rp.M) Inventory (Rp.M) Prepaid Exp. (Rp.M) Long-term Debt (Rp.M) Short-term Borrowings (Rp.M) Current Portion of LT Debt & Leases (Rp.M) Total Debt (Rp.M) Net Property, Plant & Equipment (Rp.M) Cost Of Goods Sold (Rp.M) Total Revenue (Rp.M) Operating Income (Rp.M) EBITDA (Rp.M) EBIT (Rp.M) Cash from Ops. (Rp.M) Net Change in Cash (Rp.M) ECS Total Common Shares Outstanding (actual) Market Capitalization (Rp.M) Year Close Stock Price (Rp.) Total Capital (Rp.M) Cash & Short-term Investments (Rp.M) Net Intangibles (Rp.M) IPO Year
0 AALI 2008 PT Astra Agro Lestari Tbk (IDX:AALI) 5336576.000000 2715518.000000 0 <NA> 0 <NA> <NA> <NA> Consumer 1988 Consumer Staples Food, Beverage and Tobacco 79.680000 2.100000 NaN 1183215.000000 6519791.000000 1.944000 0.878000 959489.000000 1975656.000000 1016167.000000 781363.000000 37429.000000 NaN NaN NaN 0.000000 4123645.000000 4357818.000000 8161217.000000 3370969.000000 3616720.000000 3370969.000000 2087429.000000 -145096.000000 1574745000.000000 15432501.000000 9800.000000 5336576.000000 867676.000000 NaN 1997
1 AALI 2009 PT Astra Agro Lestari Tbk (IDX:AALI) 6426616.000000 1729648.000000 0 <NA> 0 <NA> <NA> <NA> Consumer 1988 Consumer Staples Food, Beverage and Tobacco 79.680000 2.100000 NaN 1144783.000000 7571399.000000 1.826000 1.007000 775450.000000 1714426.000000 938976.000000 610031.000000 36849.000000 NaN NaN NaN 0.000000 5242447.000000 4322498.000000 7424283.000000 2603280.000000 2898342.000000 2603280.000000 1984894.000000 -79127.000000 1574745000.000000 35825448.750000 22750.000000 6426616.000000 788549.000000 NaN 1997
2 AALI 2010 PT Astra Agro Lestari Tbk (IDX:AALI) 7457257.000000 2103652.000000 0 <NA> 0 <NA> <NA> <NA> Consumer 1988 Consumer Staples Food, Beverage and Tobacco 79.680000 2.100000 NaN 1334542.000000 8791799.000000 1.932000 1.262000 989325.000000 2051177.000000 1061852.000000 624694.000000 22315.000000 NaN NaN NaN 0.000000 6103150.000000 5234372.000000 8843721.000000 2992793.000000 3335987.000000 2992793.000000 2946657.000000 452232.000000 1574745000.000000 41258319.000000 26200.000000 7457257.000000 1240781.000000 NaN 1997
3 AALI 2011 PT Astra Agro Lestari Tbk (IDX:AALI) 8426158.000000 2498565.000000 0 <NA> 0 <NA> <NA> <NA> Consumer 1988 Consumer Staples Food, Beverage and Tobacco 79.680000 2.100000 NaN 1778337.000000 10204495.000000 1.265000 0.582000 389456.000000 1857025.000000 1467569.000000 769903.000000 NaN NaN NaN NaN 0.000000 7702571.000000 6837674.000000 10772582.000000 3195661.000000 3572651.000000 3195661.000000 3162475.000000 -402591.000000 1574745000.000000 34171966.500000 21700.000000 8426158.000000 838190.000000 NaN 1997
4 AALI 2012 PT Astra Agro Lestari Tbk (IDX:AALI) 9365411.000000 2520266.000000 0 <NA> 0 <NA> <NA> <NA> Consumer 1988 Consumer Staples Food, Beverage and Tobacco 79.680000 2.100000 NaN 4701077.000000 12419820.000000 0.685000 0.107000 -820145.000000 1780395.000000 2600540.000000 1249050.000000 NaN NaN 971950.000000 NaN 971950.000000 9894266.000000 7206837.000000 11564319.000000 3453729.000000 3973578.000000 3453729.000000 2609511.000000 -610421.000000 1574745000.000000 31022476.500000 19700.000000 10337361.000000 227769.000000 NaN 1997
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
8484 ZONE 2023 PT Mega Perintis Tbk (IDX:ZONE) 376089.685000 46972.766000 <NA> <NA> <NA> <NA> 0 <NA> Consumer 2005 Consumer Discretionary Consumer Discretionary Distribution and Retail NaN NaN 62.030000 376866.895000 752956.580000 1.787000 0.183000 187443.551000 425743.875000 238300.324000 370381.888000 8229.119000 45537.359000 85403.842000 NaN 230078.796000 254442.974000 327373.203000 735452.174000 80467.569000 109639.609000 80467.569000 101276.087000 -292.586000 870171478.000000 965890.340580 1110.000000 606168.481000 4617.740000 36266.170000 2018
8485 ZONE 2024 PT Mega Perintis Tbk (IDX:ZONE) 370674.214000 7044.347000 <NA> <NA> <NA> <NA> <NA> <NA> Consumer 2005 Consumer Discretionary Consumer Discretionary Distribution and Retail NaN NaN 62.030000 378069.858000 748744.072000 1.750000 0.178000 175199.380000 408802.739000 233603.359000 356567.003000 9325.106000 37101.093000 111850.141000 NaN 262139.509000 262571.197000 347862.483000 708360.249000 33764.860000 68145.652000 33764.860000 92620.371000 3123.094000 870171478.000000 717891.469350 825.000000 632813.723000 7740.834000 36707.806000 2018
8486 ZYRX 2022 PT Zyrexindo Mandiri Buana Tbk (IDX:ZYRX) 268289.329000 78627.417000 0 <NA> 0 <NA> 0 <NA> Technology, Media & Telecommunications 1996 Information Technology Technology Hardware and Equipment NaN NaN 75.160000 438940.489000 707229.819000 1.442000 0.668000 190967.877000 623252.109000 432284.232000 321857.019000 32.094000 NaN 154814.692000 NaN 154972.192000 62069.361000 626487.712000 770370.215000 124050.223000 126197.456000 124050.223000 108226.350000 205303.596000 1333333837.000000 426666.704320 320.000000 423261.521000 206376.347000 NaN 2021
8487 ZYRX 2023 PT Zyrexindo Mandiri Buana Tbk (IDX:ZYRX) 289448.779000 32952.892000 <NA> <NA> <NA> <NA> 0 <NA> Technology, Media & Telecommunications 1996 Information Technology Technology Hardware and Equipment NaN NaN 75.160000 201592.951000 491041.729000 2.073000 0.401000 208245.855000 402244.527000 193998.671000 320652.316000 1146.130000 136.413000 123103.971000 NaN 123328.362000 68457.678000 198332.059000 289633.302000 75643.212000 77211.203000 75643.212000 -128496.218000 -193672.184000 1333334556.000000 221333.536296 166.000000 412777.140000 12704.163000 149.572000 2021
8488 ZYRX 2024 PT Zyrexindo Mandiri Buana Tbk (IDX:ZYRX) 300050.888000 15171.892000 <NA> <NA> <NA> <NA> <NA> <NA> Technology, Media & Telecommunications 1996 Information Technology Technology Hardware and Equipment NaN NaN 75.160000 92393.711000 392444.599000 3.402000 0.775000 201817.814000 285854.627000 84036.813000 219621.479000 1021.593000 41.627000 46780.579000 NaN 47050.482000 92660.098000 296495.058000 364238.674000 37276.725000 38767.823000 37276.725000 113472.348000 -6431.849000 1333334556.000000 177333.495948 133.000000 347101.370000 6272.314000 388.017000 2021

8489 rows × 46 columns

Analisis

Unique value dan null

print('Isi Sector :')
print(df_findat_integrated['Sector'].unique())
print('\nIsi 1st Level Primary Industry:')
print(df_findat_integrated['1st Level Primary Industry'].unique())
print('\nIsi Indusry Group')
print(df_findat_integrated['Industry Group'].unique())
Isi Sector :
['Consumer Staples' 'Communication Services' 'Financials' 'Energy'
 'Consumer Discretionary' 'Industrials' 'Real Estate' 'Materials'
 'Utilities' 'Information Technology' 'Health Care']

Isi 1st Level Primary Industry:
['Consumer' 'Technology, Media & Telecommunications' 'Financials'
 'Energy and Utilities' 'Industrials' 'Real Estate' 'Materials'
 'Health Care']

Isi Indusry Group
['Food, Beverage and Tobacco' 'Media and Entertainment' 'Insurance'
 'Energy' 'Consumer Discretionary Distribution and Retail' 'Capital Goods'
 'Real Estate Management and Development' 'Financial Services' 'Materials'
 'Automobiles and Components' 'Banks' 'Consumer Services' 'Transportation'
 'Consumer Staples Distribution and Retail'
 'Consumer Durables and Apparel' 'Utilities'
 'Commercial and Professional Services' 'Software and Services'
 'Technology Hardware and Equipment' 'Telecommunication Services'
 'Health Care Equipment and Services'
 'Pharmaceuticals, Biotechnology and Life Sciences'
 'Household and Personal Products']
(df_findat_integrated[["Sector", "1st Level Primary Industry", "Industry Group"]]
  .agg(["count", "nunique"])
  .T
  .assign(missing=lambda x: len(df_findat_final) - x["count"])
)
count nunique missing
variable
Sector 8489 11 0
1st Level Primary Industry 8489 8 0
Industry Group 8489 23 0

Cek pemetaan dari yg paling detail ke yang lebih general

# Berapa banyak Sector unik untuk tiap Industry Group?
ig_to_sector_n = (df_findat_integrated
                  .groupby("Industry Group")["Sector"]
                  .nunique(dropna=True)
                  .sort_values(ascending=False))

ig_to_sector_n  # kalau semuanya 1, berarti deterministik
Sector
Industry Group
Automobiles and Components 1
Banks 1
Capital Goods 1
Commercial and Professional Services 1
Consumer Discretionary Distribution and Retail 1
Consumer Durables and Apparel 1
Consumer Services 1
Consumer Staples Distribution and Retail 1
Energy 1
Financial Services 1
Food, Beverage and Tobacco 1
Health Care Equipment and Services 1
Household and Personal Products 1
Insurance 1
Materials 1
Media and Entertainment 1
Pharmaceuticals, Biotechnology and Life Sciences 1
Real Estate Management and Development 1
Software and Services 1
Technology Hardware and Equipment 1
Telecommunication Services 1
Transportation 1
Utilities 1

ig_to_primary_n = (df_findat_integrated
                   .groupby("Industry Group")["1st Level Primary Industry"]
                   .nunique(dropna=True)
                   .sort_values(ascending=False))
ig_to_primary_n
1st Level Primary Industry
Industry Group
Capital Goods 2
Consumer Services 2
Consumer Discretionary Distribution and Retail 2
Software and Services 2
Financial Services 2
Banks 1
Automobiles and Components 1
Consumer Durables and Apparel 1
Commercial and Professional Services 1
Energy 1
Consumer Staples Distribution and Retail 1
Health Care Equipment and Services 1
Household and Personal Products 1
Insurance 1
Food, Beverage and Tobacco 1
Materials 1
Media and Entertainment 1
Pharmaceuticals, Biotechnology and Life Sciences 1
Real Estate Management and Development 1
Technology Hardware and Equipment 1
Telecommunication Services 1
Transportation 1
Utilities 1

Lihat tiap tipe sektor/industri terhadap distress

targets = ["target_distress_ppk", "target_distress_neg_equity", "target_distress_consecutive_loss"]
def distress_rate_multi_targets(df, group_col, target_cols):
    print('Distress rate pada fitur', group_col)
    n = df.groupby(group_col).size().rename("n")
    rates = df.groupby(group_col)[target_cols].mean()
    return pd.concat([n, rates], axis=1).reset_index()
distress_rate_multi_targets(df_findat_integrated, "Industry Group", targets).sort_values(by=targets, ascending=False)
Distress rate pada fitur Industry Group
Industry Group n target_distress_ppk target_distress_neg_equity target_distress_consecutive_loss
6 Consumer Services 360 0.333333 0.021352 0.214286
5 Consumer Durables and Apparel 323 0.287234 0.037037 0.172840
15 Media and Entertainment 254 0.269663 0.036269 0.167832
17 Real Estate Management and Development 854 0.251852 0.004399 0.115163
2 Capital Goods 769 0.228814 0.028269 0.129032
3 Commercial and Professional Services 132 0.228571 0.000000 0.065217
21 Transportation 544 0.220339 0.053731 0.198276
14 Materials 1019 0.204082 0.014851 0.111831
8 Energy 665 0.201087 0.037895 0.150150
18 Software and Services 82 0.194444 0.000000 0.238095
9 Financial Services 419 0.183486 0.000000 0.072165
20 Telecommunication Services 208 0.180328 0.023529 0.093750
4 Consumer Discretionary Distribution and Retail 322 0.174757 0.028436 0.122222
10 Food, Beverage and Tobacco 802 0.159533 0.011475 0.080235
7 Consumer Staples Distribution and Retail 159 0.155556 0.015625 0.107843
13 Insurance 277 0.146341 0.000000 0.008696
19 Technology Hardware and Equipment 93 0.138889 0.050847 0.119048
22 Utilities 65 0.125000 0.044444 0.125000
11 Health Care Equipment and Services 146 0.120690 0.020833 0.103896
16 Pharmaceuticals, Biotechnology and Life Sciences 129 0.117647 0.018182 0.047619
0 Automobiles and Components 131 0.117647 0.000000 0.060000
12 Household and Personal Products 101 0.085714 0.000000 0.092308
1 Banks 635 0.078212 0.001961 0.048729
distress_rate_multi_targets(df_findat_integrated, "Sector", targets).sort_values(by=targets, ascending=False)
Distress rate pada fitur Sector
Sector n target_distress_ppk target_distress_neg_equity target_distress_consecutive_loss
9 Real Estate 854 0.251852 0.004399 0.115163
1 Consumer Discretionary 1136 0.251462 0.023990 0.154908
0 Communication Services 462 0.233333 0.030303 0.132841
6 Industrials 1445 0.225446 0.033764 0.141952
8 Materials 1019 0.204082 0.014851 0.111831
3 Energy 665 0.201087 0.037895 0.150150
7 Information Technology 175 0.166667 0.027523 0.178571
2 Consumer Staples 1062 0.151335 0.011029 0.085546
10 Utilities 65 0.125000 0.044444 0.125000
4 Financials 1331 0.124324 0.000909 0.046324
5 Health Care 275 0.119565 0.019417 0.071429
distress_rate_multi_targets(df_findat_integrated, "1st Level Primary Industry", targets).sort_values(by=targets, ascending=False)
Distress rate pada fitur 1st Level Primary Industry
1st Level Primary Industry n target_distress_ppk target_distress_neg_equity target_distress_consecutive_loss
6 Real Estate 871 0.257353 0.004304 0.116858
4 Industrials 1428 0.227477 0.034274 0.144703
7 Technology, Media & Telecommunications 638 0.210762 0.029661 0.143662
5 Materials 1019 0.204082 0.014851 0.111831
0 Consumer 2210 0.198830 0.017295 0.116605
1 Energy and Utilities 730 0.192308 0.038462 0.147453
2 Financials 1318 0.122951 0.000918 0.046843
3 Health Care 275 0.119565 0.019417 0.071429
vc = df_findat_integrated["Industry Group"].value_counts()
print("min:", vc.min(), "median:", vc.median(), "max:", vc.max(), "max/min:", vc.max()/vc.min())
vc
min: 65 median: 277.0 max: 1019 max/min: 15.676923076923076
count
Industry Group
Materials 1019
Real Estate Management and Development 854
Food, Beverage and Tobacco 802
Capital Goods 769
Energy 665
Banks 635
Transportation 544
Financial Services 419
Consumer Services 360
Consumer Durables and Apparel 323
Consumer Discretionary Distribution and Retail 322
Insurance 277
Media and Entertainment 254
Telecommunication Services 208
Consumer Staples Distribution and Retail 159
Health Care Equipment and Services 146
Commercial and Professional Services 132
Automobiles and Components 131
Pharmaceuticals, Biotechnology and Life Sciences 129
Household and Personal Products 101
Technology Hardware and Equipment 93
Software and Services 82
Utilities 65

Berikut ini insight yang ditemukan untuk ketiga kasus distress dengan range prediksi within 2 years:

Terlihat bahwa Industry Group merupakan fitur yang paling detail dengan banyak nilai unik, sedangkan fitur 1st Level Primary Industry merupakan fitur yang paling general dengan sedikit nilai unik. Apabila Industry Group dipetakan terhadap Sector hasilnya terlihat terpetakan 1-on-1, sedangkan terhadap 1st Level Primary Industry hasilnya ada 5 yang 2-on-1 dan kebanyakan sisanya tetap 1-on-1.

Pada jenis Financials dari fitur Sector, ditemukan bahwa rate distressnya sebesar 0.143. Namun, pada jenis Banks, Financial Services, dan Insurance dari fitur Industry Group, terlihat bahwa ketiganya memiliki rate distress yang bervariasi, ada yang lebih tinggi dan ada yang lebih rendah dari 0.143. Hal ini menunjukkan bahwa fitur Industry Group memiliki kemungkinan untuk dapat menjadi prediktor atau setidaknya pemisah yang lebih baik daripada Sector yang terlalu general, begitu pula fitur 1st Level Primary Industry. Dengan demikian, pada tahap selanjutnya, fitur Industry Group dipilih untuk merepresentasikan pada sektor/industri apa perusahaan beroperasi, sedangkan fitur lainnya akan didrop untuk mengurangi redundansi.

Pilih Kolom Industry Group

# Pilih kolom 'Industry Group' yang akan dipertahankan
df_findat_integrated = df_findat_integrated.drop(['Sector', '1st Level Primary Industry'], axis=1)
columns_to_check = ['Sector', '1st Level Primary Industry', 'Industry Group']
print('CEK KOLOM PADA df_findat_integrated :')
for col in columns_to_check:
    if col in df_findat_integrated.columns:
        print(f"- '{col}' ADA.")
    else:
        print(f"- '{col}' TIDAK ADA.")
CEK KOLOM PADA df_findat_integrated :
- 'Sector' TIDAK ADA.
- '1st Level Primary Industry' TIDAK ADA.
- 'Industry Group' ADA.

(?) Null Handling Pre-Building

Dilakukan khusus pada dataset yang dihilangkan nullnya.

df_findat_integrated_with_null = df_findat_integrated.copy()
null_counts_fitur = df_findat_integrated_with_null.isnull().sum()
total_rows_fitur = len(df_findat_integrated_with_null)
null_percentages_fitur = (null_counts_fitur / total_rows_fitur) * 100

null_summary_fitur_df = pd.DataFrame({
    'Null Count': null_counts_fitur,
    'Null Pctg': null_percentages_fitur
})

null_summary_fitur_df = null_summary_fitur_df.sort_values(by='Null Count', ascending=False)

print("Summary Null Values di df_findat_integrated_with_null:")
print(null_summary_fitur_df.to_string())
Summary Null Values di df_findat_integrated_with_null:
                                              Null Count  Null Pctg
variable                                                           
Current Portion of LT Debt & Leases (Rp.M)          8152  96.030157
tahun_distress_neg_equity                           7416  87.360113
Net Intangibles (Rp.M)                              6726  79.231947
target_distress_ppk                                 5906  69.572388
tahun_distress_ppk                                  5068  59.700789
tahun_distress_consecutive_loss                     4320  50.889386
Percent Owned - All Institutions (%)                3947  46.495465
Parent Percent Owned (%)                            3641  42.890800
target_distress_consecutive_loss                    3329  39.215455
Short-term Borrowings (Rp.M)                        2910  34.279656
Long-term Debt (Rp.M)                               2581  30.404052
Percent Owned - Insiders (%)                        2551  30.050654
target_distress_neg_equity                          2086  24.572977
Prepaid Exp. (Rp.M)                                 1462  17.222288
Inventory (Rp.M)                                    1206  14.206620
EBITDA (Rp.M)                                       1106  13.028625
EBIT (Rp.M)                                         1099  12.946166
Current Ratio (x)                                    706   8.316645
Quick Ratio (x)                                      704   8.293085
Cost Of Goods Sold (Rp.M)                            180   2.120391
Total Revenue (Rp.M)                                  31   0.365178
Year Close Stock Price (Rp.)                          25   0.294499
Market Capitalization (Rp.M)                          25   0.294499
Total Debt (Rp.M)                                     21   0.247379
Total Capital (Rp.M)                                  21   0.247379
Year Established                                      21   0.247379
Net Property, Plant & Equipment (Rp.M)                19   0.223819
Net Change in Cash (Rp.M)                              2   0.023560
Cash from Ops. (Rp.M)                                  2   0.023560
Operating Income (Rp.M)                                1   0.011780
ECS Total Common Shares Outstanding (actual)           1   0.011780
Net Income to Company (Rp.M)                           0   0.000000
ticker                                                 0   0.000000
Year                                                   0   0.000000
Entity Name                                            0   0.000000
Total Equity (Rp.M)                                    0   0.000000
Total Current Assets (Rp.M)                            0   0.000000
Working Capital (Rp.M)                                 0   0.000000
Total Current Liabilities (Rp.M)                       0   0.000000
Total Liabilities (Rp.M)                               0   0.000000
Total Assets (Rp.M)                                    0   0.000000
Industry Group                                         0   0.000000
Cash & Short-term Investments (Rp.M)                   0   0.000000
IPO Year                                               0   0.000000
null_counts_per_row_fitur = df_findat_integrated_with_null.isnull().sum(axis=1)
null_counts_grouped_fitur = null_counts_per_row_fitur.value_counts().sort_index(ascending=False)

print("Jumlah baris berdasarkan jumlah nilai null (df_findat_integrated_with_null, diurutkan dari yang terbanyak):")
print(null_counts_grouped_fitur.to_string())
Jumlah baris berdasarkan jumlah nilai null (df_findat_integrated_with_null, diurutkan dari yang terbanyak):
18       1
17       2
16      12
15      17
14      46
13     171
12     264
11     406
10     715
9      961
8     1475
7     1761
6     1490
5      784
4      331
3       49
2        4
print('Baris dengan banyak null (>25)')
df_findat_integrated_with_null[df_findat_integrated_with_null.isna().sum(axis=1)>25]
Baris dengan banyak null (>25)
variable ticker Year Entity Name Total Equity (Rp.M) Net Income to Company (Rp.M) target_distress_consecutive_loss tahun_distress_consecutive_loss target_distress_neg_equity tahun_distress_neg_equity target_distress_ppk tahun_distress_ppk Year Established Industry Group Parent Percent Owned (%) Percent Owned - All Institutions (%) Percent Owned - Insiders (%) Total Liabilities (Rp.M) Total Assets (Rp.M) Current Ratio (x) Quick Ratio (x) Working Capital (Rp.M) Total Current Assets (Rp.M) Total Current Liabilities (Rp.M) Inventory (Rp.M) Prepaid Exp. (Rp.M) Long-term Debt (Rp.M) Short-term Borrowings (Rp.M) Current Portion of LT Debt & Leases (Rp.M) Total Debt (Rp.M) Net Property, Plant & Equipment (Rp.M) Cost Of Goods Sold (Rp.M) Total Revenue (Rp.M) Operating Income (Rp.M) EBITDA (Rp.M) EBIT (Rp.M) Cash from Ops. (Rp.M) Net Change in Cash (Rp.M) ECS Total Common Shares Outstanding (actual) Market Capitalization (Rp.M) Year Close Stock Price (Rp.) Total Capital (Rp.M) Cash & Short-term Investments (Rp.M) Net Intangibles (Rp.M) IPO Year

Feature Building

Fungsi Penyusun Fitur

def build_features(df_raw):
    df = df_raw.copy()

    # Function Pembagi
    # bagi dengan aman: jika 0 atau NaN -> NaN, bukan inf.
    def safe_div(num, denom):
        # if isinstance(denom, pd.Series) and (denom == 0).any():
        #     print('dibagi 0!!!')
        # elif not isinstance(denom, pd.Series) and denom == 0:
        #     print('dibagi 0!!!')
        res = num / denom
        return res.replace([np.inf, -np.inf], np.nan)

    # Fungsi log agar bisa menerima negatif
    # input 0 hasilnya juga 0 dengan menambahkan angka sangat kecil
    small_num = 1e-6
    def signed_log(x):
        return np.sign(x) * np.log(np.abs(x) + small_num)

    # Sort & Reset Index
    df = df.sort_values(["ticker", "Year"]).reset_index(drop=True)

    # Nama fitur
    CA      = df['Total Current Assets (Rp.M)']
    CL      = df['Total Current Liabilities (Rp.M)']
    TA      = df['Total Assets (Rp.M)']
    TL      = df['Total Liabilities (Rp.M)']
    Equity  = df['Total Equity (Rp.M)']
    Debt_rep = df['Total Debt (Rp.M)']

    LTD     = df['Long-term Debt (Rp.M)']
    STB     = df['Short-term Borrowings (Rp.M)']
    CPLTD   = df['Current Portion of LT Debt & Leases (Rp.M)']

    Inventory = df['Inventory (Rp.M)']
    Prepaid   = df['Prepaid Exp. (Rp.M)']
    PPE       = df['Net Property, Plant & Equipment (Rp.M)']

    Sales   = df['Total Revenue (Rp.M)']
    COGS    = df['Cost Of Goods Sold (Rp.M)']
    OpInc   = df['Operating Income (Rp.M)']
    EBITDA  = df['EBITDA (Rp.M)']
    EBIT    = df['EBIT (Rp.M)']
    NI      = df['Net Income to Company (Rp.M)']

    CFO     = df['Cash from Ops. (Rp.M)']
    NetCash = df['Net Change in Cash (Rp.M)']

    CashST  = df['Cash & Short-term Investments (Rp.M)']
    Intang  = df['Net Intangibles (Rp.M)']

    Shares  = df['ECS Total Common Shares Outstanding (actual)']
    MktCap = df['Market Capitalization (Rp.M)']
    Price   = df['Year Close Stock Price (Rp.)']
    TotalCap = df['Total Capital (Rp.M)']

    WC = df['Working Capital (Rp.M)']
    Debt = df['Total Debt (Rp.M)']

    Sales_log = signed_log(Sales)
    MktCap_log = signed_log(MktCap)
    TA_log = signed_log(TA)

    # Umur perusahaan
    AgeWhenIPO = df['IPO Year'] - df['Year Established']
    YearsSinceIPO = df['Year'] - df['IPO Year']

    # Net debt
    NetDebt = Debt - CashST


    #### SUSUN NUMERIK ####

    # fitur dasar identitas & struktur
    df['Age_When_IPO'] = AgeWhenIPO
    df['Years_Since_IPO'] = YearsSinceIPO

    # --- LIQUIDITY & WORKING CAPITAL ----
    df = df.rename(columns={"Current Ratio (x)": "CR", "Quick Ratio (x)": "QR"})

    # df['WC_TA'] = safe_div(WC, TA) # UDAH DI ALTMAN
    df['WC_Sales'] = safe_div(WC, Sales)

    df['CA_TA'] = safe_div(CA, TA)
    df['CL_TA'] = safe_div(CL, TA)
    df['CashST_TA'] = safe_div(CashST, TA)
    df['CashST_CL'] = safe_div(CashST, CL)
    df['Inventory_CA'] = safe_div(Inventory, CA)
    df['Prepaid_CA'] = safe_div(Prepaid, CA)

    # --- LEVERAGE & CAPITAL STRUCTURE ---
    df['TL_TA'] = safe_div(TL, TA)
    df['Debt_TA'] = safe_div(Debt, TA)
    df['Debt_Equity'] = safe_div(Debt, Equity) # DER
    df['Equity_TA'] = safe_div(Equity, TA)
    df['NetDebt_EBITDA'] = safe_div(NetDebt, EBITDA) # Leverage Ratio

    df['PPE_TA'] = safe_div(PPE, TA)
    df['Intang_TA'] = safe_div(Intang, TA)

    # Altman-like komponen yang bisa dihitung
    df['Altman_X1_WC_TA'] = safe_div(WC, TA)
    df['Altman_X3_EBIT_TA'] = safe_div(EBIT, TA)
    df['Altman_X4_MVE_TL'] = safe_div(MktCap, TL)
    df['Altman_X5_SalesTA_AssetTurnover'] = safe_div(Sales, TA)

    # --- PROFITABILITY & MARGINS ---
    df['ROA'] = safe_div(NI, TA)
    df['ROE'] = safe_div(NI, Equity)
    # df['EBIT_TA'] = safe_div(EBIT, TA) # UDAH DI ALTMAN
    df['EBITDA_TA'] = safe_div(EBITDA, TA)

    df['GrossMargin'] = safe_div(Sales - COGS, Sales)
    df['OpMargin'] = safe_div(OpInc, Sales)
    df['NetMargin'] = safe_div(NI, Sales)

    # --- CASH FLOW BASED ---
    df['CFO_TA'] = safe_div(CFO, TA)
    df['CFO_TL'] = safe_div(CFO, TL)
    df['CFO_Sales'] = safe_div(CFO, Sales)
    df['NetCash_TA'] = safe_div(NetCash, TA)

    # --- EFFICIENCY SEDERHANA (TURNOVER) ---
    # df['AssetTurnover'] = safe_div(Sales, TA) # UDAH DI ALTMAN

    # --- PER SHARE & MARKET ---
    df['EPS_proxy'] = safe_div(NI, Shares) * 1_000_000     # Rp per saham, dibuaat proporsional thd aproksimasi EPS karena ketidaktersediaan data cc
    df['Sales_per_share'] = safe_div(Sales, Shares) * 1_000_000
    df['CFO_per_share'] = safe_div(CFO, Shares) * 1_000_000

    df['log_TA'] = TA_log
    df['log_Sales'] = Sales_log
    df['log_MktCap'] = MktCap_log
    df['PB'] = safe_div(MktCap, Equity)
    # TODO : Bisa tambahkan P/E, nnti cek dulu unit NI & Shares

    #### FITUR GROWTH YoY ####

    # Tambahkan fill_method=None untuk menghilangkan warning dan memastikan akurasi
    df['Sales_growth'] = df.groupby('ticker')['Total Revenue (Rp.M)'].pct_change(fill_method=None)
    df['NI_growth']    = df.groupby('ticker')['Net Income to Company (Rp.M)'].pct_change(fill_method=None)
    df['TA_growth']    = df.groupby('ticker')['Total Assets (Rp.M)'].pct_change(fill_method=None)
    df['Equity_growth'] = df.groupby('ticker')['Total Equity (Rp.M)'].pct_change(fill_method=None)
    df['CFO_growth']    = df.groupby('ticker')['Cash from Ops. (Rp.M)'].pct_change(fill_method=None)

    #### ENCODING FITUR KATEGORIKAL ####

    # Buat copy kolom untuk dipakai analisis (dihapus lagi nanti)
    df['Industry_Group_Copy'] = df['Industry Group'].str.replace(',', '')

    # One-hot encoding untuk Industry Group
    df_fitur_result = pd.get_dummies(
        df,
        columns=['Industry_Group_Copy'],
        prefix='industry',
        drop_first=True
    )

    #### URUTKAN KOLOM HASIL AKHIR ####

    first_cols = [
        "ticker",
        "Year",
        "Entity Name",
        "target_distress_ppk",
        "tahun_distress_ppk",
        "Total Equity (Rp.M)",
        "target_distress_neg_equity",
        "tahun_distress_neg_equity",
        "Net Income to Company (Rp.M)",
        "target_distress_consecutive_loss",
        "tahun_distress_consecutive_loss"
    ]
    first_cols = [c for c in first_cols if c in df_fitur_result.columns]
    df_fitur_result = df_fitur_result[first_cols + [c for c in df_fitur_result.columns if c not in first_cols]]

    # Delete Columns
    # sementara disimpan dulu

    return df_fitur_result.copy()
df_findat_integrated[['Year', 'Year Established', 'IPO Year']].dtypes
0
variable
Year Int64
Year Established Int64
IPO Year Int64

Execute Feature Building

Dataset disini masih mempertahankan null

df_fitur = build_features(df_findat_integrated)
df_fitur
ticker Year Entity Name target_distress_ppk tahun_distress_ppk Total Equity (Rp.M) target_distress_neg_equity tahun_distress_neg_equity Net Income to Company (Rp.M) target_distress_consecutive_loss tahun_distress_consecutive_loss Year Established Industry Group Parent Percent Owned (%) Percent Owned - All Institutions (%) Percent Owned - Insiders (%) Total Liabilities (Rp.M) Total Assets (Rp.M) CR QR Working Capital (Rp.M) Total Current Assets (Rp.M) Total Current Liabilities (Rp.M) Inventory (Rp.M) Prepaid Exp. (Rp.M) Long-term Debt (Rp.M) Short-term Borrowings (Rp.M) Current Portion of LT Debt & Leases (Rp.M) Total Debt (Rp.M) Net Property, Plant & Equipment (Rp.M) Cost Of Goods Sold (Rp.M) Total Revenue (Rp.M) Operating Income (Rp.M) EBITDA (Rp.M) EBIT (Rp.M) Cash from Ops. (Rp.M) Net Change in Cash (Rp.M) ECS Total Common Shares Outstanding (actual) Market Capitalization (Rp.M) Year Close Stock Price (Rp.) Total Capital (Rp.M) Cash & Short-term Investments (Rp.M) Net Intangibles (Rp.M) IPO Year Age_When_IPO Years_Since_IPO WC_Sales CA_TA CL_TA CashST_TA CashST_CL Inventory_CA Prepaid_CA TL_TA Debt_TA Debt_Equity Equity_TA NetDebt_EBITDA PPE_TA Intang_TA Altman_X1_WC_TA Altman_X3_EBIT_TA Altman_X4_MVE_TL Altman_X5_SalesTA_AssetTurnover ROA ROE EBITDA_TA GrossMargin OpMargin NetMargin CFO_TA CFO_TL CFO_Sales NetCash_TA EPS_proxy Sales_per_share CFO_per_share log_TA log_Sales log_MktCap PB Sales_growth NI_growth TA_growth Equity_growth CFO_growth industry_Banks industry_Capital Goods industry_Commercial and Professional Services industry_Consumer Discretionary Distribution and Retail industry_Consumer Durables and Apparel industry_Consumer Services industry_Consumer Staples Distribution and Retail industry_Energy industry_Financial Services industry_Food Beverage and Tobacco industry_Health Care Equipment and Services industry_Household and Personal Products industry_Insurance industry_Materials industry_Media and Entertainment industry_Pharmaceuticals Biotechnology and Life Sciences industry_Real Estate Management and Development industry_Software and Services industry_Technology Hardware and Equipment industry_Telecommunication Services industry_Transportation industry_Utilities
0 AALI 2008 PT Astra Agro Lestari Tbk (IDX:AALI) <NA> <NA> 5336576.000000 0 <NA> 2715518.000000 0 <NA> 1988 Food, Beverage and Tobacco 79.680000 2.100000 NaN 1183215.000000 6519791.000000 1.944000 0.878000 959489.000000 1975656.000000 1016167.000000 781363.000000 37429.000000 NaN NaN NaN 0.000000 4123645.000000 4357818.000000 8161217.000000 3370969.000000 3616720.000000 3370969.000000 2087429.000000 -145096.000000 1574745000.000000 15432501.000000 9800.000000 5336576.000000 867676.000000 NaN 1997 9 11 0.117567 0.303024 0.155859 0.133083 0.853871 0.395495 0.018945 0.181481 0.000000 0.000000 0.818519 -0.239907 0.632481 NaN 0.147166 0.517036 13.042854 1.251761 0.416504 0.508850 0.554729 0.466033 0.413047 0.332734 0.320168 1.764201 0.255774 -0.022255 1724.417604 5182.564161 1325.566362 15.690353 15.914904 16.551986 2.891836 NaN NaN NaN NaN NaN False False False False False False False False False True False False False False False False False False False False False False
1 AALI 2009 PT Astra Agro Lestari Tbk (IDX:AALI) <NA> <NA> 6426616.000000 0 <NA> 1729648.000000 0 <NA> 1988 Food, Beverage and Tobacco 79.680000 2.100000 NaN 1144783.000000 7571399.000000 1.826000 1.007000 775450.000000 1714426.000000 938976.000000 610031.000000 36849.000000 NaN NaN NaN 0.000000 5242447.000000 4322498.000000 7424283.000000 2603280.000000 2898342.000000 2603280.000000 1984894.000000 -79127.000000 1574745000.000000 35825448.750000 22750.000000 6426616.000000 788549.000000 NaN 1997 9 12 0.104448 0.226435 0.124016 0.104148 0.839797 0.355822 0.021493 0.151198 0.000000 0.000000 0.848802 -0.272069 0.692401 NaN 0.102418 0.343831 31.294532 0.980570 0.228445 0.269138 0.382801 0.417789 0.350644 0.232972 0.262157 1.733860 0.267352 -0.010451 1098.367037 4714.593791 1260.454232 15.839888 15.820267 17.394169 5.574543 -0.090297 -0.363050 0.161295 0.204258 -0.049120 False False False False False False False False False True False False False False False False False False False False False False
2 AALI 2010 PT Astra Agro Lestari Tbk (IDX:AALI) <NA> <NA> 7457257.000000 0 <NA> 2103652.000000 0 <NA> 1988 Food, Beverage and Tobacco 79.680000 2.100000 NaN 1334542.000000 8791799.000000 1.932000 1.262000 989325.000000 2051177.000000 1061852.000000 624694.000000 22315.000000 NaN NaN NaN 0.000000 6103150.000000 5234372.000000 8843721.000000 2992793.000000 3335987.000000 2992793.000000 2946657.000000 452232.000000 1574745000.000000 41258319.000000 26200.000000 7457257.000000 1240781.000000 NaN 1997 9 13 0.111868 0.233306 0.120778 0.141129 1.168507 0.304554 0.010879 0.151794 0.000000 0.000000 0.848206 -0.371938 0.694187 NaN 0.112528 0.340407 30.915714 1.005906 0.239274 0.282095 0.379443 0.408126 0.338409 0.237870 0.335160 2.207991 0.333192 0.051438 1335.868347 5615.970205 1871.196289 15.989330 15.995218 17.535363 5.532640 0.191189 0.216231 0.161186 0.160371 0.484541 False False False False False False False False False True False False False False False False False False False False False False
3 AALI 2011 PT Astra Agro Lestari Tbk (IDX:AALI) <NA> <NA> 8426158.000000 0 <NA> 2498565.000000 0 <NA> 1988 Food, Beverage and Tobacco 79.680000 2.100000 NaN 1778337.000000 10204495.000000 1.265000 0.582000 389456.000000 1857025.000000 1467569.000000 769903.000000 NaN NaN NaN NaN 0.000000 7702571.000000 6837674.000000 10772582.000000 3195661.000000 3572651.000000 3195661.000000 3162475.000000 -402591.000000 1574745000.000000 34171966.500000 21700.000000 8426158.000000 838190.000000 NaN 1997 9 14 0.036153 0.181981 0.143816 0.082139 0.571142 0.414589 NaN 0.174270 0.000000 0.000000 0.825730 -0.234613 0.754821 NaN 0.038165 0.313162 19.215687 1.055670 0.244849 0.296525 0.350106 0.365271 0.296648 0.231937 0.309910 1.778333 0.293567 -0.039452 1586.647362 6840.842168 2008.245779 16.138339 16.192515 17.346916 4.055462 0.218105 0.187727 0.160683 0.129927 0.073242 False False False False False False False False False True False False False False False False False False False False False False
4 AALI 2012 PT Astra Agro Lestari Tbk (IDX:AALI) <NA> <NA> 9365411.000000 0 <NA> 2520266.000000 0 <NA> 1988 Food, Beverage and Tobacco 79.680000 2.100000 NaN 4701077.000000 12419820.000000 0.685000 0.107000 -820145.000000 1780395.000000 2600540.000000 1249050.000000 NaN NaN 971950.000000 NaN 971950.000000 9894266.000000 7206837.000000 11564319.000000 3453729.000000 3973578.000000 3453729.000000 2609511.000000 -610421.000000 1574745000.000000 31022476.500000 19700.000000 10337361.000000 227769.000000 NaN 1997 9 15 -0.070920 0.143351 0.209386 0.018339 0.087585 0.701558 NaN 0.378514 0.078258 0.103781 0.754070 0.187282 0.796651 NaN -0.066035 0.278082 6.599015 0.931118 0.202923 0.269104 0.319938 0.376804 0.298654 0.217935 0.210109 0.555088 0.225652 -0.049149 1600.428006 7343.613728 1657.100673 16.334804 16.263435 17.250223 3.312452 0.073496 0.008685 0.217093 0.111469 -0.174852 False False False False False False False False False True False False False False False False False False False False False False
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
8484 ZONE 2023 PT Mega Perintis Tbk (IDX:ZONE) 0 <NA> 376089.685000 <NA> <NA> 46972.766000 <NA> <NA> 2005 Consumer Discretionary Distribution and Retail NaN NaN 62.030000 376866.895000 752956.580000 1.787000 0.183000 187443.551000 425743.875000 238300.324000 370381.888000 8229.119000 45537.359000 85403.842000 NaN 230078.796000 254442.974000 327373.203000 735452.174000 80467.569000 109639.609000 80467.569000 101276.087000 -292.586000 870171478.000000 965890.340580 1110.000000 606168.481000 4617.740000 36266.170000 2018 13 5 0.254868 0.565430 0.316486 0.006133 0.019378 0.869964 0.019329 0.500516 0.305567 0.611766 0.499484 2.056383 0.337925 0.048165 0.248943 0.106869 2.562948 0.976752 0.062384 0.124898 0.145612 0.554868 0.109412 0.063869 0.134505 0.268732 0.137706 -0.000389 53.981045 845.180740 116.386356 13.531763 13.508241 13.780806 2.568245 0.092989 -0.356013 0.155229 0.084306 -0.015960 False False False True False False False False False False False False False False False False False False False False False False
8485 ZONE 2024 PT Mega Perintis Tbk (IDX:ZONE) <NA> <NA> 370674.214000 <NA> <NA> 7044.347000 <NA> <NA> 2005 Consumer Discretionary Distribution and Retail NaN NaN 62.030000 378069.858000 748744.072000 1.750000 0.178000 175199.380000 408802.739000 233603.359000 356567.003000 9325.106000 37101.093000 111850.141000 NaN 262139.509000 262571.197000 347862.483000 708360.249000 33764.860000 68145.652000 33764.860000 92620.371000 3123.094000 870171478.000000 717891.469350 825.000000 632813.723000 7740.834000 36707.806000 2018 13 6 0.247331 0.545985 0.311994 0.010338 0.033137 0.872223 0.022811 0.504939 0.350106 0.707197 0.495061 3.733161 0.350682 0.049026 0.233991 0.045095 1.898833 0.946065 0.009408 0.019004 0.091013 0.508919 0.047666 0.009945 0.123701 0.244982 0.130753 0.004171 8.095355 814.046733 106.439217 13.526153 13.470708 13.484074 1.936718 -0.036837 -0.850033 -0.005595 -0.014399 -0.085467 False False False True False False False False False False False False False False False False False False False False False False
8486 ZYRX 2022 PT Zyrexindo Mandiri Buana Tbk (IDX:ZYRX) 0 <NA> 268289.329000 0 <NA> 78627.417000 0 <NA> 1996 Technology Hardware and Equipment NaN NaN 75.160000 438940.489000 707229.819000 1.442000 0.668000 190967.877000 623252.109000 432284.232000 321857.019000 32.094000 NaN 154814.692000 NaN 154972.192000 62069.361000 626487.712000 770370.215000 124050.223000 126197.456000 124050.223000 108226.350000 205303.596000 1333333837.000000 426666.704320 320.000000 423261.521000 206376.347000 NaN 2021 25 1 0.247891 0.881258 0.611236 0.291809 0.477409 0.516415 0.000051 0.620648 0.219126 0.577631 0.379352 -0.407331 0.087764 NaN 0.270022 0.175403 0.972038 1.089278 0.111177 0.293069 0.178439 0.186771 0.161027 0.102064 0.153029 0.246563 0.140486 0.290293 58.970540 577.777443 81.169732 13.469111 13.554626 12.963758 1.590323 NaN NaN NaN NaN NaN False False False False False False False False False False False False False False False False False False True False False False
8487 ZYRX 2023 PT Zyrexindo Mandiri Buana Tbk (IDX:ZYRX) 0 <NA> 289448.779000 <NA> <NA> 32952.892000 <NA> <NA> 1996 Technology Hardware and Equipment NaN NaN 75.160000 201592.951000 491041.729000 2.073000 0.401000 208245.855000 402244.527000 193998.671000 320652.316000 1146.130000 136.413000 123103.971000 NaN 123328.362000 68457.678000 198332.059000 289633.302000 75643.212000 77211.203000 75643.212000 -128496.218000 -193672.184000 1333334556.000000 221333.536296 166.000000 412777.140000 12704.163000 149.572000 2021 25 2 0.718998 0.819166 0.395076 0.025872 0.065486 0.797158 0.002849 0.410541 0.251157 0.426080 0.589459 1.432748 0.139413 0.000305 0.424090 0.154046 1.097923 0.589834 0.067108 0.113847 0.157240 0.315230 0.261169 0.113775 -0.261681 -0.637404 -0.443651 -0.394411 24.714646 217.224777 -96.372075 13.104284 12.576371 12.307426 0.764673 -0.624034 -0.580898 -0.305683 0.078868 -2.187291 False False False False False False False False False False False False False False False False False False True False False False
8488 ZYRX 2024 PT Zyrexindo Mandiri Buana Tbk (IDX:ZYRX) <NA> <NA> 300050.888000 <NA> <NA> 15171.892000 <NA> <NA> 1996 Technology Hardware and Equipment NaN NaN 75.160000 92393.711000 392444.599000 3.402000 0.775000 201817.814000 285854.627000 84036.813000 219621.479000 1021.593000 41.627000 46780.579000 NaN 47050.482000 92660.098000 296495.058000 364238.674000 37276.725000 38767.823000 37276.725000 113472.348000 -6431.849000 1333334556.000000 177333.495948 133.000000 347101.370000 6272.314000 388.017000 2021 25 3 0.554081 0.728395 0.214137 0.015983 0.074638 0.768298 0.003574 0.235431 0.119891 0.156808 0.764569 1.051856 0.236110 0.000989 0.514258 0.094986 1.919324 0.928128 0.038660 0.050564 0.098785 0.185987 0.102341 0.041654 0.289142 1.228139 0.311533 -0.016389 11.378909 273.178755 85.104183 12.880151 12.805565 12.085787 0.591011 0.257586 -0.539588 -0.200792 0.036629 -1.883079 False False False False False False False False False False False False False False False False False False True False False False

8489 rows × 108 columns

Hapus Kolom Raw Number

exclude = {'ticker', 'Year', 'Entity Name', 'Parent Percent Owned (%)', 'Percent Owned - All Institutions (%)', 'Percent Owned - Insiders (%)', 'Industry Group'}

raw_cols = [c for c in df_fitur.columns if c in set(df_findat_final.columns) and c not in exclude]
raw_cols
['Total Equity (Rp.M)',
 'Net Income to Company (Rp.M)',
 'Year Established',
 'Total Liabilities (Rp.M)',
 'Total Assets (Rp.M)',
 'Working Capital (Rp.M)',
 'Total Current Assets (Rp.M)',
 'Total Current Liabilities (Rp.M)',
 'Inventory (Rp.M)',
 'Prepaid Exp. (Rp.M)',
 'Long-term Debt (Rp.M)',
 'Short-term Borrowings (Rp.M)',
 'Current Portion of LT Debt & Leases (Rp.M)',
 'Total Debt (Rp.M)',
 'Net Property, Plant & Equipment (Rp.M)',
 'Cost Of Goods Sold (Rp.M)',
 'Total Revenue (Rp.M)',
 'Operating Income (Rp.M)',
 'EBITDA (Rp.M)',
 'EBIT (Rp.M)',
 'Cash from Ops. (Rp.M)',
 'Net Change in Cash (Rp.M)',
 'ECS Total Common Shares Outstanding (actual)',
 'Market Capitalization (Rp.M)',
 'Year Close Stock Price (Rp.)',
 'Total Capital (Rp.M)',
 'Cash & Short-term Investments (Rp.M)',
 'Net Intangibles (Rp.M)',
 'IPO Year']
df_fitur = df_fitur.drop(columns=raw_cols)
df_fitur
ticker Year Entity Name target_distress_ppk tahun_distress_ppk target_distress_neg_equity tahun_distress_neg_equity target_distress_consecutive_loss tahun_distress_consecutive_loss Industry Group Parent Percent Owned (%) Percent Owned - All Institutions (%) Percent Owned - Insiders (%) CR QR Age_When_IPO Years_Since_IPO WC_Sales CA_TA CL_TA CashST_TA CashST_CL Inventory_CA Prepaid_CA TL_TA Debt_TA Debt_Equity Equity_TA NetDebt_EBITDA PPE_TA Intang_TA Altman_X1_WC_TA Altman_X3_EBIT_TA Altman_X4_MVE_TL Altman_X5_SalesTA_AssetTurnover ROA ROE EBITDA_TA GrossMargin OpMargin NetMargin CFO_TA CFO_TL CFO_Sales NetCash_TA EPS_proxy Sales_per_share CFO_per_share log_TA log_Sales log_MktCap PB Sales_growth NI_growth TA_growth Equity_growth CFO_growth industry_Banks industry_Capital Goods industry_Commercial and Professional Services industry_Consumer Discretionary Distribution and Retail industry_Consumer Durables and Apparel industry_Consumer Services industry_Consumer Staples Distribution and Retail industry_Energy industry_Financial Services industry_Food Beverage and Tobacco industry_Health Care Equipment and Services industry_Household and Personal Products industry_Insurance industry_Materials industry_Media and Entertainment industry_Pharmaceuticals Biotechnology and Life Sciences industry_Real Estate Management and Development industry_Software and Services industry_Technology Hardware and Equipment industry_Telecommunication Services industry_Transportation industry_Utilities
0 AALI 2008 PT Astra Agro Lestari Tbk (IDX:AALI) <NA> <NA> 0 <NA> 0 <NA> Food, Beverage and Tobacco 79.680000 2.100000 NaN 1.944000 0.878000 9 11 0.117567 0.303024 0.155859 0.133083 0.853871 0.395495 0.018945 0.181481 0.000000 0.000000 0.818519 -0.239907 0.632481 NaN 0.147166 0.517036 13.042854 1.251761 0.416504 0.508850 0.554729 0.466033 0.413047 0.332734 0.320168 1.764201 0.255774 -0.022255 1724.417604 5182.564161 1325.566362 15.690353 15.914904 16.551986 2.891836 NaN NaN NaN NaN NaN False False False False False False False False False True False False False False False False False False False False False False
1 AALI 2009 PT Astra Agro Lestari Tbk (IDX:AALI) <NA> <NA> 0 <NA> 0 <NA> Food, Beverage and Tobacco 79.680000 2.100000 NaN 1.826000 1.007000 9 12 0.104448 0.226435 0.124016 0.104148 0.839797 0.355822 0.021493 0.151198 0.000000 0.000000 0.848802 -0.272069 0.692401 NaN 0.102418 0.343831 31.294532 0.980570 0.228445 0.269138 0.382801 0.417789 0.350644 0.232972 0.262157 1.733860 0.267352 -0.010451 1098.367037 4714.593791 1260.454232 15.839888 15.820267 17.394169 5.574543 -0.090297 -0.363050 0.161295 0.204258 -0.049120 False False False False False False False False False True False False False False False False False False False False False False
2 AALI 2010 PT Astra Agro Lestari Tbk (IDX:AALI) <NA> <NA> 0 <NA> 0 <NA> Food, Beverage and Tobacco 79.680000 2.100000 NaN 1.932000 1.262000 9 13 0.111868 0.233306 0.120778 0.141129 1.168507 0.304554 0.010879 0.151794 0.000000 0.000000 0.848206 -0.371938 0.694187 NaN 0.112528 0.340407 30.915714 1.005906 0.239274 0.282095 0.379443 0.408126 0.338409 0.237870 0.335160 2.207991 0.333192 0.051438 1335.868347 5615.970205 1871.196289 15.989330 15.995218 17.535363 5.532640 0.191189 0.216231 0.161186 0.160371 0.484541 False False False False False False False False False True False False False False False False False False False False False False
3 AALI 2011 PT Astra Agro Lestari Tbk (IDX:AALI) <NA> <NA> 0 <NA> 0 <NA> Food, Beverage and Tobacco 79.680000 2.100000 NaN 1.265000 0.582000 9 14 0.036153 0.181981 0.143816 0.082139 0.571142 0.414589 NaN 0.174270 0.000000 0.000000 0.825730 -0.234613 0.754821 NaN 0.038165 0.313162 19.215687 1.055670 0.244849 0.296525 0.350106 0.365271 0.296648 0.231937 0.309910 1.778333 0.293567 -0.039452 1586.647362 6840.842168 2008.245779 16.138339 16.192515 17.346916 4.055462 0.218105 0.187727 0.160683 0.129927 0.073242 False False False False False False False False False True False False False False False False False False False False False False
4 AALI 2012 PT Astra Agro Lestari Tbk (IDX:AALI) <NA> <NA> 0 <NA> 0 <NA> Food, Beverage and Tobacco 79.680000 2.100000 NaN 0.685000 0.107000 9 15 -0.070920 0.143351 0.209386 0.018339 0.087585 0.701558 NaN 0.378514 0.078258 0.103781 0.754070 0.187282 0.796651 NaN -0.066035 0.278082 6.599015 0.931118 0.202923 0.269104 0.319938 0.376804 0.298654 0.217935 0.210109 0.555088 0.225652 -0.049149 1600.428006 7343.613728 1657.100673 16.334804 16.263435 17.250223 3.312452 0.073496 0.008685 0.217093 0.111469 -0.174852 False False False False False False False False False True False False False False False False False False False False False False
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
8484 ZONE 2023 PT Mega Perintis Tbk (IDX:ZONE) 0 <NA> <NA> <NA> <NA> <NA> Consumer Discretionary Distribution and Retail NaN NaN 62.030000 1.787000 0.183000 13 5 0.254868 0.565430 0.316486 0.006133 0.019378 0.869964 0.019329 0.500516 0.305567 0.611766 0.499484 2.056383 0.337925 0.048165 0.248943 0.106869 2.562948 0.976752 0.062384 0.124898 0.145612 0.554868 0.109412 0.063869 0.134505 0.268732 0.137706 -0.000389 53.981045 845.180740 116.386356 13.531763 13.508241 13.780806 2.568245 0.092989 -0.356013 0.155229 0.084306 -0.015960 False False False True False False False False False False False False False False False False False False False False False False
8485 ZONE 2024 PT Mega Perintis Tbk (IDX:ZONE) <NA> <NA> <NA> <NA> <NA> <NA> Consumer Discretionary Distribution and Retail NaN NaN 62.030000 1.750000 0.178000 13 6 0.247331 0.545985 0.311994 0.010338 0.033137 0.872223 0.022811 0.504939 0.350106 0.707197 0.495061 3.733161 0.350682 0.049026 0.233991 0.045095 1.898833 0.946065 0.009408 0.019004 0.091013 0.508919 0.047666 0.009945 0.123701 0.244982 0.130753 0.004171 8.095355 814.046733 106.439217 13.526153 13.470708 13.484074 1.936718 -0.036837 -0.850033 -0.005595 -0.014399 -0.085467 False False False True False False False False False False False False False False False False False False False False False False
8486 ZYRX 2022 PT Zyrexindo Mandiri Buana Tbk (IDX:ZYRX) 0 <NA> 0 <NA> 0 <NA> Technology Hardware and Equipment NaN NaN 75.160000 1.442000 0.668000 25 1 0.247891 0.881258 0.611236 0.291809 0.477409 0.516415 0.000051 0.620648 0.219126 0.577631 0.379352 -0.407331 0.087764 NaN 0.270022 0.175403 0.972038 1.089278 0.111177 0.293069 0.178439 0.186771 0.161027 0.102064 0.153029 0.246563 0.140486 0.290293 58.970540 577.777443 81.169732 13.469111 13.554626 12.963758 1.590323 NaN NaN NaN NaN NaN False False False False False False False False False False False False False False False False False False True False False False
8487 ZYRX 2023 PT Zyrexindo Mandiri Buana Tbk (IDX:ZYRX) 0 <NA> <NA> <NA> <NA> <NA> Technology Hardware and Equipment NaN NaN 75.160000 2.073000 0.401000 25 2 0.718998 0.819166 0.395076 0.025872 0.065486 0.797158 0.002849 0.410541 0.251157 0.426080 0.589459 1.432748 0.139413 0.000305 0.424090 0.154046 1.097923 0.589834 0.067108 0.113847 0.157240 0.315230 0.261169 0.113775 -0.261681 -0.637404 -0.443651 -0.394411 24.714646 217.224777 -96.372075 13.104284 12.576371 12.307426 0.764673 -0.624034 -0.580898 -0.305683 0.078868 -2.187291 False False False False False False False False False False False False False False False False False False True False False False
8488 ZYRX 2024 PT Zyrexindo Mandiri Buana Tbk (IDX:ZYRX) <NA> <NA> <NA> <NA> <NA> <NA> Technology Hardware and Equipment NaN NaN 75.160000 3.402000 0.775000 25 3 0.554081 0.728395 0.214137 0.015983 0.074638 0.768298 0.003574 0.235431 0.119891 0.156808 0.764569 1.051856 0.236110 0.000989 0.514258 0.094986 1.919324 0.928128 0.038660 0.050564 0.098785 0.185987 0.102341 0.041654 0.289142 1.228139 0.311533 -0.016389 11.378909 273.178755 85.104183 12.880151 12.805565 12.085787 0.591011 0.257586 -0.539588 -0.200792 0.036629 -1.883079 False False False False False False False False False False False False False False False False False False True False False False

8489 rows × 79 columns

Hasil Kolom Tersisa

df_fitur.columns.to_list()
['ticker',
 'Year',
 'Entity Name',
 'target_distress_ppk',
 'tahun_distress_ppk',
 'target_distress_neg_equity',
 'tahun_distress_neg_equity',
 'target_distress_consecutive_loss',
 'tahun_distress_consecutive_loss',
 'Industry Group',
 'Parent Percent Owned (%)',
 'Percent Owned - All Institutions (%)',
 'Percent Owned - Insiders (%)',
 'CR',
 'QR',
 'Age_When_IPO',
 'Years_Since_IPO',
 'WC_Sales',
 'CA_TA',
 'CL_TA',
 'CashST_TA',
 'CashST_CL',
 'Inventory_CA',
 'Prepaid_CA',
 'TL_TA',
 'Debt_TA',
 'Debt_Equity',
 'Equity_TA',
 'NetDebt_EBITDA',
 'PPE_TA',
 'Intang_TA',
 'Altman_X1_WC_TA',
 'Altman_X3_EBIT_TA',
 'Altman_X4_MVE_TL',
 'Altman_X5_SalesTA_AssetTurnover',
 'ROA',
 'ROE',
 'EBITDA_TA',
 'GrossMargin',
 'OpMargin',
 'NetMargin',
 'CFO_TA',
 'CFO_TL',
 'CFO_Sales',
 'NetCash_TA',
 'EPS_proxy',
 'Sales_per_share',
 'CFO_per_share',
 'log_TA',
 'log_Sales',
 'log_MktCap',
 'PB',
 'Sales_growth',
 'NI_growth',
 'TA_growth',
 'Equity_growth',
 'CFO_growth',
 'industry_Banks',
 'industry_Capital Goods',
 'industry_Commercial and Professional Services',
 'industry_Consumer Discretionary Distribution and Retail',
 'industry_Consumer Durables and Apparel',
 'industry_Consumer Services',
 'industry_Consumer Staples Distribution and Retail',
 'industry_Energy',
 'industry_Financial Services',
 'industry_Food Beverage and Tobacco',
 'industry_Health Care Equipment and Services',
 'industry_Household and Personal Products',
 'industry_Insurance',
 'industry_Materials',
 'industry_Media and Entertainment',
 'industry_Pharmaceuticals Biotechnology and Life Sciences',
 'industry_Real Estate Management and Development',
 'industry_Software and Services',
 'industry_Technology Hardware and Equipment',
 'industry_Telecommunication Services',
 'industry_Transportation',
 'industry_Utilities']

Feature Data Understanding

Sebaran Data

targets = [col for col in df_fitur.columns if col.startswith('target')]
excluded_cols = [
    'ticker', 'Year', 'Entity Name',
    'tahun_distress_ppk', 'tahun_distress_neg_equity', 'tahun_distress_consecutive_loss'
]

# --- 1. Persiapan & Pemisahan Kolom ---
cols_to_exclude = targets + excluded_cols
potential_cols = [col for col in df_fitur.columns if col not in cols_to_exclude]

numeric_cols = []
categorical_cols = []

for col in potential_cols:
    # Cek prioritas: Industry -> Boolean -> Object -> Numeric
    if col.startswith('industry') or df_fitur[col].dtype == 'bool' or df_fitur[col].dtype == 'object':
        categorical_cols.append(col)
    elif pd.api.types.is_numeric_dtype(df_fitur[col]):
        numeric_cols.append(col)

print(f"Total Baris Data: {len(df_fitur)}")
print(f"Fitur Numerik: {len(numeric_cols)}")
print(f"Fitur Kategorikal: {len(categorical_cols)}")
Total Baris Data: 8489
Fitur Numerik: 47
Fitur Kategorikal: 23
# =========================
# PILIH KOLOM NUMERIK SAJA
# =========================
targets = [col for col in df_fitur.columns if col.startswith('target')]
industries = [col for col in df_fitur.columns if col.startswith('industry')]
excluded_cols = [
    'ticker', 'Year', 'Entity Name',
    'tahun_distress_ppk', 'tahun_distress_neg_equity', 'tahun_distress_consecutive_loss'
]

cols_to_exclude = set(targets + excluded_cols + industries)
potential_cols = [col for col in df_fitur.columns if col not in cols_to_exclude]

numeric_cols = [
    col for col in potential_cols
    if pd.api.types.is_numeric_dtype(df_fitur[col])
]

print(f"Total Baris Data: {len(df_fitur)}")
print(f"Fitur Numerik: {len(numeric_cols)}")

N = len(df_fitur)

# =========================
# VISUALISASI NUMERIK (Boxplot + Hist)
# =========================
features_per_page = 5
num_batches = math.ceil(len(numeric_cols) / features_per_page)

MAX_POINTS_PLOT = 80_000   # sampling agar plotting stabil (tidak mengubah df)
MAX_POINTS_KDE  = 8_000    # KDE hanya kalau data kecil (hindari hang)
HIST_BINS       = 50       # bins tetap agar cepat & konsisten

for i in range(num_batches):
    start = i * features_per_page
    end = min((i + 1) * features_per_page, len(numeric_cols))
    batch = numeric_cols[start:end]

    fig, axes = plt.subplots(len(batch), 2, figsize=(16, 4 * len(batch)))
    fig.suptitle(f'Distribusi Numerik Part {i+1}', fontsize=16, fontweight='bold', y=1.01)

    if len(batch) == 1:
        axes = [axes]

    for idx, col in enumerate(batch):
        ax_box = axes[idx][0]
        ax_hist = axes[idx][1]

        try:
            s_raw = df_fitur[col]

            # hitung null (tanpa mengubah df)
            null_count = int(s_raw.isna().sum())
            null_prop = null_count / N if N else 0.0

            # bersihkan untuk plotting: inf -> nan, coerce numeric, dropna sementara
            s_clean = (
                s_raw.replace([np.inf, -np.inf], np.nan)
                     .pipe(pd.to_numeric, errors="coerce")
                     .dropna()
            )

            if s_clean.empty:
                msg = f"ALL NULL/INF | Null: {null_count} ({null_prop:.1%})"
                ax_box.set_title(f"Outlier Check: {col}", fontsize=10, fontweight='bold')
                ax_box.text(0.5, 0.5, msg, ha='center', va='center')
                ax_hist.set_title(msg, fontsize=10)
                ax_hist.text(0.5, 0.5, "No data to plot", ha='center', va='center')
                continue

            # sampling agar tidak berat
            s_plot = s_clean.sample(MAX_POINTS_PLOT, random_state=42) if len(s_clean) > MAX_POINTS_PLOT else s_clean

            # statistik (dari non-null)
            desc = s_plot.describe()
            skew = s_plot.skew()

            stats_text = (
                f"Min: {desc['min']:.2f} | Max: {desc['max']:.2f} | "
                f"Mean: {desc['mean']:.2f} | Med: {desc['50%']:.2f} | "
                f"Skew: {skew:.2f} | "
                f"Null: {null_count} ({null_prop:.1%})"
            )

            # boxplot
            sns.boxplot(x=s_plot, ax=ax_box, color='#3498db')
            ax_box.set_title(f"Outlier Check: {col}", fontsize=10, fontweight='bold')
            ax_box.set_xlabel('')

            # hist (+ KDE dibatasi agar tidak hang)
            use_kde = (s_plot.nunique() > 1) and (len(s_plot) <= MAX_POINTS_KDE)
            sns.histplot(s_plot, bins=HIST_BINS, kde=use_kde, ax=ax_hist, color='#e67e22', stat='density')

            ax_hist.axvline(desc['mean'], color='red', linestyle='--', label='Mean')
            ax_hist.axvline(desc['50%'], color='green', linestyle='-', label='Median')
            ax_hist.set_title(stats_text, fontsize=10)
            ax_hist.legend(loc='upper right', fontsize='small')
            ax_hist.set_xlabel('')

        except Exception as e:
            err = f"{type(e).__name__}: {e}"
            ax_box.clear()
            ax_hist.clear()
            ax_box.set_title(f"Outlier Check: {col}", fontsize=10, fontweight='bold')
            ax_box.text(0.5, 0.5, f"Error:\n{err}", ha='center', va='center', wrap=True)
            ax_hist.set_title(f"Error on: {col}", fontsize=10)
            ax_hist.text(0.5, 0.5, f"Error:\n{err}", ha='center', va='center', wrap=True)

    plt.tight_layout()
    plt.show()
    plt.close(fig)
    plt.close('all')
Total Baris Data: 8489
Fitur Numerik: 47

# Menghitung min dan max, lalu di-transpose (.T) agar nama kolom berjejer ke bawah
min_max_summary = df_fitur[numeric_cols].agg(['min', 'max']).T

print("=== Ringkasan Nilai Minimum dan Maksimum ===")
print(min_max_summary)
=== Ringkasan Nilai Minimum dan Maksimum ===
                                                  min            max
Parent Percent Owned (%)                     1.010000     100.000000
Percent Owned - All Institutions (%)         0.000000      99.350000
Percent Owned - Insiders (%)                 0.000000      92.500000
CR                                           0.000000     276.365000
QR                                           0.000000     297.354000
Age_When_IPO                                 0.000000     184.000000
Years_Since_IPO                              1.000000      47.000000
WC_Sales                                -34330.458213   15731.271526
CA_TA                                        0.001489       1.000000
CL_TA                                        0.000026    3183.852925
CashST_TA                                    0.000004       0.998560
CashST_CL                                    0.000128    8055.691012
Inventory_CA                                 0.000000       2.029279
Prepaid_CA                                   0.000000       0.680819
TL_TA                                        0.000102    3191.109276
Debt_TA                                      0.000000    1089.080293
Debt_Equity                              -2932.733637     121.548567
Equity_TA                                -3190.109289       0.999898
NetDebt_EBITDA                           -7836.128283    9588.426487
PPE_TA                                       0.000000       0.989719
Intang_TA                                    0.000000       1.337714
Altman_X1_WC_TA                          -3183.211894       0.995606
Altman_X3_EBIT_TA                         -428.451925       1.373432
Altman_X4_MVE_TL                             0.002928    6996.499691
Altman_X5_SalesTA_AssetTurnover             -1.172519      28.823814
ROA                                      -1391.146305    3612.433265
ROE                                      -1453.379504     139.022391
EBITDA_TA                                 -110.412651      19.026523
GrossMargin                              -1312.195304       9.075947
OpMargin                                 -1685.703170      91.388504
NetMargin                                -1672.147241     984.260833
CFO_TA                                     -90.064793       1.950671
CFO_TL                                     -92.228120      58.936858
CFO_Sales                                -3425.034582     797.276777
NetCash_TA                                  -6.540044       0.848656
EPS_proxy                            -25683068.783069  918674.008205
Sales_per_share                      -23102507.936508 2127660.925358
CFO_per_share                         -9389487.162303   17421.566813
log_TA                                       4.167146      21.610014
log_Sales                                  -16.212612      19.617387
log_MktCap                                   8.621553      24.636501
PB                                       -1848.307716   38071.722785
Sales_growth                              -265.130822    1923.627045
NI_growth                                -6386.715890    3864.340840
TA_growth                                   -0.999342    3154.072957
Equity_growth                             -871.179934    1060.375319
CFO_growth                                -613.065049    1397.118210
targets = [col for col in df_fitur.columns if col.startswith('target')]
years = [col for col in df_fitur.columns if col.startswith('tahun_distress')]
industries = [col for col in df_fitur.columns if col.startswith('industry')]
excluded_cols = [
    'ticker', 'Year', 'Entity Name',
] + targets + years + industries

dist_cols = [col for col in df_fitur.select_dtypes(include='number').columns.tolist() if col not in excluded_cols]

ncols = 6
nrows = int(np.ceil(len(dist_cols) / ncols))
fig, axes = plt.subplots(nrows, ncols, figsize=(4 * ncols, 3 * nrows))
for ax, col in zip(axes.flatten(), dist_cols):
    data = df_fitur[col].dropna()
    ax.hist(data, bins=50, color='steelblue')
    ax.set_title(col, fontsize=18)
    ax.tick_params(labelsize=7)
for ax in axes.flatten()[len(dist_cols):]:
    ax.axis('off')
fig.suptitle('Distribusi Nilai Kolom Numerik', y=1.002, fontsize=30, fontweight='bold')
plt.tight_layout()
plt.show()

targets = [col for col in df_fitur.columns if col.startswith('target')]
years = [col for col in df_fitur.columns if col.startswith('tahun_distress')]
industries = [col for col in df_fitur.columns if col.startswith('industry')]
excluded_cols = [
    'ticker', 'Year', 'Entity Name',
] + targets + years + industries

dist_cols = [col for col in df_fitur.select_dtypes(include='number').columns.tolist() if col not in excluded_cols]

ncols = 6
nrows = int(np.ceil(len(dist_cols) / ncols))
fig, axes = plt.subplots(nrows, ncols, figsize=(4 * ncols, 3 * nrows))
for ax, col in zip(axes.flatten(), dist_cols):
    data = df_fitur[col].dropna()
    ax.boxplot(data, vert=False,
               patch_artist=True,
               boxprops=dict(facecolor='steelblue', color='steelblue'),
               medianprops=dict(color='black'))
    ax.set_title(col, fontsize=18)
    ax.tick_params(labelsize=7)
for ax in axes.flatten()[len(dist_cols):]:
    ax.axis('off')
fig.suptitle('Distribusi Nilai Kolom Numerik', y=1.002, fontsize=30, fontweight='bold')
plt.tight_layout()
plt.show()

Korelasi Antar Kolom

Pearson

corr_matrix_pearson = (
    df_fitur[numeric_cols]
    .apply(pd.to_numeric, errors="coerce")
    .corr()
)

mask = np.triu(np.ones_like(corr_matrix_pearson, dtype=bool))

plt.figure(figsize=(18, 14))
sns.heatmap(
    corr_matrix_pearson,
    mask=mask,
    cmap="RdBu_r",
    center=0,
    vmin=-1,
    vmax=1,
    linewidths=0.3,
    cbar_kws={"shrink": 0.8}
)
plt.title("Heatmap Korelasi (PEARSON) Antar Kolom Numerik pada df_fitur", fontsize=14, fontweight="bold")
plt.xticks(rotation=45, ha="right")
plt.yticks(rotation=0)
plt.tight_layout()
plt.show()

# 1. Tentukan ambang batas (threshold) korelasi tinggi, misalnya 0.8 atau 0.85
threshold = 0.80

# 2. Ambil nilai absolut dari matriks korelasi
corr_abs_pearson = corr_matrix_pearson.abs()

# 3. Buat "topeng" (mask) untuk mengambil nilai di segitiga atas (upper triangle)
# k=1 berfungsi untuk membuang garis diagonal utama (korelasi dengan diri sendiri)
upper_triangle = corr_abs_pearson.where(np.triu(np.ones(corr_abs_pearson.shape), k=1).astype(bool))

# 4. Ubah format menjadi series berjejer (stack) dan saring berdasarkan threshold
high_corr_pairs = upper_triangle.stack()[upper_triangle.stack() > threshold]

# 5. Urutkan dari korelasi tertinggi ke terendah, lalu cetak
high_corr_pairs = high_corr_pairs.sort_values(ascending=False)

print(f"Daftar pasangan fitur dengan korelasi Pearson > {threshold}:\n")
for (fitur_1, fitur_2), nilai_absolut in high_corr_pairs.items():
    # Mengambil nilai asli (positif/negatif) dari matriks awal
    nilai_asli = corr_matrix_pearson.loc[fitur_1, fitur_2]
    
    print(f"{fitur_1} & {fitur_2} : {nilai_asli:.4f}")
Daftar pasangan fitur dengan korelasi Pearson > 0.8:

TL_TA & Equity_TA : -1.0000
CL_TA & Altman_X1_WC_TA : -1.0000
CL_TA & Equity_TA : -0.9996
CL_TA & TL_TA : 0.9996
Equity_TA & Altman_X1_WC_TA : 0.9996
TL_TA & Altman_X1_WC_TA : -0.9996
EPS_proxy & Sales_per_share : 0.9984
Debt_TA & Equity_TA : -0.9981
TL_TA & Debt_TA : 0.9981
Altman_X1_WC_TA & Altman_X3_EBIT_TA : 0.9971
CL_TA & Altman_X3_EBIT_TA : -0.9970
CL_TA & Debt_TA : 0.9964
Debt_TA & Altman_X1_WC_TA : -0.9964
Equity_TA & Altman_X3_EBIT_TA : 0.9960
TL_TA & Altman_X3_EBIT_TA : -0.9960
Altman_X3_EBIT_TA & CFO_TA : 0.9940
Debt_TA & Altman_X3_EBIT_TA : -0.9899
Altman_X1_WC_TA & CFO_TA : 0.9875
CL_TA & CFO_TA : -0.9875
Equity_TA & CFO_TA : 0.9865
TL_TA & CFO_TA : -0.9865
Debt_TA & CFO_TA : -0.9798
CR & QR : 0.9522
EBITDA_TA & CFO_TA : 0.9510
Altman_X3_EBIT_TA & EBITDA_TA : 0.9494
Altman_X1_WC_TA & EBITDA_TA : 0.9355
CL_TA & EBITDA_TA : -0.9355
Equity_TA & EBITDA_TA : 0.9351
TL_TA & EBITDA_TA : -0.9351
Debt_TA & EBITDA_TA : -0.9244
QR & CashST_CL : 0.8509
log_TA & log_MktCap : 0.8266
WC_Sales & CFO_Sales : 0.8116
CR & CashST_CL : 0.8060

Spearman

corr_matrix_spearman = (
    df_fitur[numeric_cols]
    .apply(pd.to_numeric, errors="coerce")
    .corr(method='spearman')
)

mask = np.triu(np.ones_like(corr_matrix_spearman, dtype=bool))

plt.figure(figsize=(18, 14))
sns.heatmap(
    corr_matrix_spearman,
    mask=mask,
    cmap="RdBu_r",
    center=0,
    vmin=-1,
    vmax=1,
    linewidths=0.3,
    cbar_kws={"shrink": 0.8}
)
plt.title("Heatmap Korelasi (SPEARMAN) Antar Kolom Numerik pada df_fitur", fontsize=14, fontweight="bold")
plt.xticks(rotation=45, ha="right")
plt.yticks(rotation=0)
plt.tight_layout()
plt.show()

# 1. Tentukan ambang batas (threshold) korelasi tinggi, misalnya 0.8 atau 0.85
threshold = 0.80

# 2. Ambil nilai absolut dari matriks korelasi
corr_abs_spearman = corr_matrix_spearman.abs()

# 3. Buat "topeng" (mask) untuk mengambil nilai di segitiga atas (upper triangle)
# k=1 berfungsi untuk membuang garis diagonal utama (korelasi dengan diri sendiri)
upper_triangle = corr_abs_spearman.where(np.triu(np.ones(corr_abs_spearman.shape), k=1).astype(bool))

# 4. Ubah format menjadi series berjejer (stack) dan saring berdasarkan threshold
high_corr_pairs = upper_triangle.stack()[upper_triangle.stack() > threshold]

# 5. Urutkan dari korelasi tertinggi ke terendah, lalu cetak
high_corr_pairs = high_corr_pairs.sort_values(ascending=False)

print(f"Daftar pasangan fitur dengan korelasi Spearman > {threshold}:\n")
for (fitur_1, fitur_2), nilai_absolut in high_corr_pairs.items():
    # Mengambil nilai asli (positif/negatif) dari matriks awal
    nilai_asli = corr_matrix_spearman.loc[fitur_1, fitur_2]
    
    print(f"{fitur_1} & {fitur_2} : {nilai_asli:.4f}")
Daftar pasangan fitur dengan korelasi Spearman > 0.8:

TL_TA & Equity_TA : -0.9820
CFO_TA & CFO_TL : 0.9357
Altman_X3_EBIT_TA & EBITDA_TA : 0.9344
CR & Altman_X1_WC_TA : 0.9094
CR & WC_Sales : 0.8636
CFO_TA & CFO_per_share : 0.8615
Altman_X3_EBIT_TA & ROA : 0.8475
log_TA & log_MktCap : 0.8339
WC_Sales & Altman_X1_WC_TA : 0.8322
ROA & EPS_proxy : 0.8270
CashST_TA & CashST_CL : 0.8212
CR & QR : 0.8211
CFO_TA & CFO_Sales : 0.8182
ROA & ROE : 0.8065
CFO_TL & CFO_Sales : 0.8044
OpMargin & NetMargin : 0.8038
CFO_TL & CFO_per_share : 0.8012

Variance Inflation Factors (VIF)

# # 1. Siapkan data (VIF tidak bisa memproses data yang memiliki missing values/NaN)
# # Pastikan  menggunakan data yang sudah di-imputasi!
# X = df_fitur[numeric_cols].copy()

# # 2. Tambahkan konstanta (Intercept)
# # Ini adalah langkah WAJIB. Tanpa konstanta, nilai VIF akan terdistorsi menjadi sangat besar
# X = add_constant(X)

# # 3. Hitung VIF untuk setiap fitur
# vif_data = pd.DataFrame()
# vif_data["Fitur"] = X.columns
# vif_data["VIF"] = [variance_inflation_factor(X.values, i) for i in range(X.shape[1])]

# # 4. Buang baris 'const' (karena kita hanya peduli pada VIF rasio keuangannya saja)
# vif_data = vif_data[vif_data["Fitur"] != "const"]

# # 5. Urutkan dari VIF tertinggi ke terendah agar rapi di grafik
# vif_data = vif_data.sort_values(by="VIF", ascending=False)

# # ==========================================
# # BAGIAN VISUALISASI
# # ==========================================
# plt.figure(figsize=(10, max(6, len(numeric_cols) * 0.4))) # Tinggi gambar menyesuaikan jumlah fitur

# # Buat bar chart
# sns.barplot(x="VIF", y="Fitur", data=vif_data, palette="coolwarm_r")

# # Tambahkan garis putus-putus sebagai batas toleransi (Threshold)
# plt.axvline(x=10, color='red', linestyle='--', linewidth=2, label='Batas Bahaya (VIF = 10)')
# plt.axvline(x=5, color='orange', linestyle='--', linewidth=2, label='Batas Peringatan (VIF = 5)')

# # Rapikan teks dan label
# plt.title("Deteksi Multikolinearitas dengan Variance Inflation Factor (VIF)", fontsize=14, pad=15)
# plt.xlabel("Skor VIF", fontsize=12)
# plt.ylabel("Fitur / Rasio Keuangan", fontsize=12)
# plt.legend()
# plt.tight_layout()

# # Tampilkan grafik
# plt.show()

# # Cetak juga dalam bentuk tabel teks untuk dokumentasi
# print(vif_data.to_string(index=False))

Sebaran Nilai Negatif

id_cols = ['ticker', 'Year', 'Entity Name']
cols_to_check = [
    col for col in df_fitur.select_dtypes(include=['number']).columns
    if 'growth' not in col.lower()
    and col not in id_cols
    and 'distress' not in col.lower()
]

neg_counts = (df_fitur[cols_to_check] < 0).sum()

neg_proportions = (neg_counts / len(df_fitur)) * 100

# gabungkan 1 df
neg_report = pd.DataFrame({
    'Negative Count': neg_counts,
    'Percentage (%)': neg_proportions
}).sort_values(by='Negative Count', ascending=False)

print(f"Total baris dalam dataset: {len(df_fitur)}")
print("-" * 50)
print(neg_report)
Total baris dalam dataset: 8489
--------------------------------------------------
                                      Negative Count  Percentage (%)
NetCash_TA                                      4025       47.414301
NetDebt_EBITDA                                  2694       31.735187
CFO_TA                                          2529       29.791495
CFO_TL                                          2529       29.791495
CFO_per_share                                   2528       29.779715
CFO_Sales                                       2506       29.520556
Altman_X1_WC_TA                                 2391       28.165862
WC_Sales                                        2384       28.083402
ROA                                             2032       23.936859
EPS_proxy                                       2032       23.936859
NetMargin                                       1947       22.935564
ROE                                             1884       22.193427
OpMargin                                        1636       19.271999
Altman_X3_EBIT_TA                               1540       18.141124
EBITDA_TA                                        935       11.014254
GrossMargin                                      340        4.005183
Equity_TA                                        310        3.651785
Debt_Equity                                      308        3.628225
PB                                               307        3.616445
log_Sales                                         71        0.836376
Sales_per_share                                   71        0.836376
Altman_X5_SalesTA_AssetTurnover                   71        0.836376
Parent Percent Owned (%)                           0        0.000000
QR                                                 0        0.000000
Percent Owned - Insiders (%)                       0        0.000000
Percent Owned - All Institutions (%)               0        0.000000
CL_TA                                              0        0.000000
CA_TA                                              0        0.000000
Years_Since_IPO                                    0        0.000000
Age_When_IPO                                       0        0.000000
CR                                                 0        0.000000
Altman_X4_MVE_TL                                   0        0.000000
Debt_TA                                            0        0.000000
TL_TA                                              0        0.000000
CashST_TA                                          0        0.000000
Intang_TA                                          0        0.000000
PPE_TA                                             0        0.000000
CashST_CL                                          0        0.000000
Prepaid_CA                                         0        0.000000
Inventory_CA                                       0        0.000000
log_TA                                             0        0.000000
log_MktCap                                         0        0.000000
id_cols = ['ticker', 'Year', 'Entity Name']

cols_to_check = [
    col for col in df_fitur.select_dtypes(include=['number']).columns
    if 'growth' not in col.lower()
    and col not in id_cols
    and 'distress' not in col.lower()
]

df_negatif = df_fitur[(df_fitur[cols_to_check] < 0).any(axis=1)]

df_negatif
ticker Year Entity Name target_distress_ppk tahun_distress_ppk target_distress_neg_equity tahun_distress_neg_equity target_distress_consecutive_loss tahun_distress_consecutive_loss Industry Group Parent Percent Owned (%) Percent Owned - All Institutions (%) Percent Owned - Insiders (%) CR QR Age_When_IPO Years_Since_IPO WC_Sales CA_TA CL_TA CashST_TA CashST_CL Inventory_CA Prepaid_CA TL_TA Debt_TA Debt_Equity Equity_TA NetDebt_EBITDA PPE_TA Intang_TA Altman_X1_WC_TA Altman_X3_EBIT_TA Altman_X4_MVE_TL Altman_X5_SalesTA_AssetTurnover ROA ROE EBITDA_TA GrossMargin OpMargin NetMargin CFO_TA CFO_TL CFO_Sales NetCash_TA EPS_proxy Sales_per_share CFO_per_share log_TA log_Sales log_MktCap PB Sales_growth NI_growth TA_growth Equity_growth CFO_growth industry_Banks industry_Capital Goods industry_Commercial and Professional Services industry_Consumer Discretionary Distribution and Retail industry_Consumer Durables and Apparel industry_Consumer Services industry_Consumer Staples Distribution and Retail industry_Energy industry_Financial Services industry_Food Beverage and Tobacco industry_Health Care Equipment and Services industry_Household and Personal Products industry_Insurance industry_Materials industry_Media and Entertainment industry_Pharmaceuticals Biotechnology and Life Sciences industry_Real Estate Management and Development industry_Software and Services industry_Technology Hardware and Equipment industry_Telecommunication Services industry_Transportation industry_Utilities
0 AALI 2008 PT Astra Agro Lestari Tbk (IDX:AALI) <NA> <NA> 0 <NA> 0 <NA> Food, Beverage and Tobacco 79.680000 2.100000 NaN 1.944000 0.878000 9 11 0.117567 0.303024 0.155859 0.133083 0.853871 0.395495 0.018945 0.181481 0.000000 0.000000 0.818519 -0.239907 0.632481 NaN 0.147166 0.517036 13.042854 1.251761 0.416504 0.508850 0.554729 0.466033 0.413047 0.332734 0.320168 1.764201 0.255774 -0.022255 1724.417604 5182.564161 1325.566362 15.690353 15.914904 16.551986 2.891836 NaN NaN NaN NaN NaN False False False False False False False False False True False False False False False False False False False False False False
1 AALI 2009 PT Astra Agro Lestari Tbk (IDX:AALI) <NA> <NA> 0 <NA> 0 <NA> Food, Beverage and Tobacco 79.680000 2.100000 NaN 1.826000 1.007000 9 12 0.104448 0.226435 0.124016 0.104148 0.839797 0.355822 0.021493 0.151198 0.000000 0.000000 0.848802 -0.272069 0.692401 NaN 0.102418 0.343831 31.294532 0.980570 0.228445 0.269138 0.382801 0.417789 0.350644 0.232972 0.262157 1.733860 0.267352 -0.010451 1098.367037 4714.593791 1260.454232 15.839888 15.820267 17.394169 5.574543 -0.090297 -0.363050 0.161295 0.204258 -0.049120 False False False False False False False False False True False False False False False False False False False False False False
2 AALI 2010 PT Astra Agro Lestari Tbk (IDX:AALI) <NA> <NA> 0 <NA> 0 <NA> Food, Beverage and Tobacco 79.680000 2.100000 NaN 1.932000 1.262000 9 13 0.111868 0.233306 0.120778 0.141129 1.168507 0.304554 0.010879 0.151794 0.000000 0.000000 0.848206 -0.371938 0.694187 NaN 0.112528 0.340407 30.915714 1.005906 0.239274 0.282095 0.379443 0.408126 0.338409 0.237870 0.335160 2.207991 0.333192 0.051438 1335.868347 5615.970205 1871.196289 15.989330 15.995218 17.535363 5.532640 0.191189 0.216231 0.161186 0.160371 0.484541 False False False False False False False False False True False False False False False False False False False False False False
3 AALI 2011 PT Astra Agro Lestari Tbk (IDX:AALI) <NA> <NA> 0 <NA> 0 <NA> Food, Beverage and Tobacco 79.680000 2.100000 NaN 1.265000 0.582000 9 14 0.036153 0.181981 0.143816 0.082139 0.571142 0.414589 NaN 0.174270 0.000000 0.000000 0.825730 -0.234613 0.754821 NaN 0.038165 0.313162 19.215687 1.055670 0.244849 0.296525 0.350106 0.365271 0.296648 0.231937 0.309910 1.778333 0.293567 -0.039452 1586.647362 6840.842168 2008.245779 16.138339 16.192515 17.346916 4.055462 0.218105 0.187727 0.160683 0.129927 0.073242 False False False False False False False False False True False False False False False False False False False False False False
4 AALI 2012 PT Astra Agro Lestari Tbk (IDX:AALI) <NA> <NA> 0 <NA> 0 <NA> Food, Beverage and Tobacco 79.680000 2.100000 NaN 0.685000 0.107000 9 15 -0.070920 0.143351 0.209386 0.018339 0.087585 0.701558 NaN 0.378514 0.078258 0.103781 0.754070 0.187282 0.796651 NaN -0.066035 0.278082 6.599015 0.931118 0.202923 0.269104 0.319938 0.376804 0.298654 0.217935 0.210109 0.555088 0.225652 -0.049149 1600.428006 7343.613728 1657.100673 16.334804 16.263435 17.250223 3.312452 0.073496 0.008685 0.217093 0.111469 -0.174852 False False False False False False False False False True False False False False False False False False False False False False
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
8483 ZONE 2022 PT Mega Perintis Tbk (IDX:ZONE) 0 <NA> 0 <NA> 0 <NA> Consumer Discretionary Distribution and Retail NaN NaN 62.030000 1.877000 0.161000 13 4 0.259725 0.573719 0.305585 0.007534 0.024653 0.867753 0.044572 0.467846 0.309258 0.581144 0.532154 1.399138 0.324746 0.049686 0.268134 0.176153 3.509987 1.032373 0.111910 0.210295 0.215650 0.559230 0.170629 0.108400 0.157904 0.337512 0.152952 -0.002362 83.823150 773.274480 118.273962 13.387464 13.419324 13.883460 3.085819 0.450564 1.369640 0.158230 0.222280 0.033258 False False False True False False False False False False False False False False False False False False False False False False
8484 ZONE 2023 PT Mega Perintis Tbk (IDX:ZONE) 0 <NA> <NA> <NA> <NA> <NA> Consumer Discretionary Distribution and Retail NaN NaN 62.030000 1.787000 0.183000 13 5 0.254868 0.565430 0.316486 0.006133 0.019378 0.869964 0.019329 0.500516 0.305567 0.611766 0.499484 2.056383 0.337925 0.048165 0.248943 0.106869 2.562948 0.976752 0.062384 0.124898 0.145612 0.554868 0.109412 0.063869 0.134505 0.268732 0.137706 -0.000389 53.981045 845.180740 116.386356 13.531763 13.508241 13.780806 2.568245 0.092989 -0.356013 0.155229 0.084306 -0.015960 False False False True False False False False False False False False False False False False False False False False False False
8486 ZYRX 2022 PT Zyrexindo Mandiri Buana Tbk (IDX:ZYRX) 0 <NA> 0 <NA> 0 <NA> Technology Hardware and Equipment NaN NaN 75.160000 1.442000 0.668000 25 1 0.247891 0.881258 0.611236 0.291809 0.477409 0.516415 0.000051 0.620648 0.219126 0.577631 0.379352 -0.407331 0.087764 NaN 0.270022 0.175403 0.972038 1.089278 0.111177 0.293069 0.178439 0.186771 0.161027 0.102064 0.153029 0.246563 0.140486 0.290293 58.970540 577.777443 81.169732 13.469111 13.554626 12.963758 1.590323 NaN NaN NaN NaN NaN False False False False False False False False False False False False False False False False False False True False False False
8487 ZYRX 2023 PT Zyrexindo Mandiri Buana Tbk (IDX:ZYRX) 0 <NA> <NA> <NA> <NA> <NA> Technology Hardware and Equipment NaN NaN 75.160000 2.073000 0.401000 25 2 0.718998 0.819166 0.395076 0.025872 0.065486 0.797158 0.002849 0.410541 0.251157 0.426080 0.589459 1.432748 0.139413 0.000305 0.424090 0.154046 1.097923 0.589834 0.067108 0.113847 0.157240 0.315230 0.261169 0.113775 -0.261681 -0.637404 -0.443651 -0.394411 24.714646 217.224777 -96.372075 13.104284 12.576371 12.307426 0.764673 -0.624034 -0.580898 -0.305683 0.078868 -2.187291 False False False False False False False False False False False False False False False False False False True False False False
8488 ZYRX 2024 PT Zyrexindo Mandiri Buana Tbk (IDX:ZYRX) <NA> <NA> <NA> <NA> <NA> <NA> Technology Hardware and Equipment NaN NaN 75.160000 3.402000 0.775000 25 3 0.554081 0.728395 0.214137 0.015983 0.074638 0.768298 0.003574 0.235431 0.119891 0.156808 0.764569 1.051856 0.236110 0.000989 0.514258 0.094986 1.919324 0.928128 0.038660 0.050564 0.098785 0.185987 0.102341 0.041654 0.289142 1.228139 0.311533 -0.016389 11.378909 273.178755 85.104183 12.880151 12.805565 12.085787 0.591011 0.257586 -0.539588 -0.200792 0.036629 -1.883079 False False False False False False False False False False False False False False False False False False True False False False

7188 rows × 79 columns

Yang normal negatif :
- NetCash_TA - CFO_TA, CFO_TL, CFO_Sales - ROA, ROE, NetMargin, OpMargin - WC_TA, WC_Sales, Altman_X1_WCTA - Altman_X3_EBITTA, EBIT_TA - EPS_proxy

Bisa negatif tapi hati-hati :
- NetDebt_EBITDA

Sangat jarang dan perlu cek definisi :
- GrossMargin - Equity_TA, Debt_Equity - PB

Sebaran Null

features_with_null = [
    col for col in df_fitur.columns[df_fitur.isnull().any()]
    if col not in ['ticker', 'Year', 'Entity Name']
    and not col.startswith(('target', 'tahun'))
]
features_with_null
['Parent Percent Owned (%)',
 'Percent Owned - All Institutions (%)',
 'Percent Owned - Insiders (%)',
 'CR',
 'QR',
 'Age_When_IPO',
 'WC_Sales',
 'Inventory_CA',
 'Prepaid_CA',
 'Debt_TA',
 'Debt_Equity',
 'NetDebt_EBITDA',
 'PPE_TA',
 'Intang_TA',
 'Altman_X3_EBIT_TA',
 'Altman_X4_MVE_TL',
 'Altman_X5_SalesTA_AssetTurnover',
 'EBITDA_TA',
 'GrossMargin',
 'OpMargin',
 'NetMargin',
 'CFO_TA',
 'CFO_TL',
 'CFO_Sales',
 'NetCash_TA',
 'EPS_proxy',
 'Sales_per_share',
 'CFO_per_share',
 'log_Sales',
 'log_MktCap',
 'PB',
 'Sales_growth',
 'NI_growth',
 'TA_growth',
 'Equity_growth',
 'CFO_growth']

Jumlah Per Kolom dan Per Baris

for distress in ['distress_ppk', 'distress_neg_equity', 'distress_consecutive_loss']:
    print(f"\n=== {distress} ===")

    null_summary_fitur_df = pd.DataFrame({
        'Null Count': df_fitur[df_fitur['target_' + distress].notna()][features_with_null].isnull().sum(),
        'Null Pctg': (df_fitur[df_fitur['target_' + distress].notna()][features_with_null].isnull().sum() / len(df_fitur[df_fitur['target_' + distress].notna()])) * 100
    }).sort_values(by='Null Count', ascending=False)

    null_summary_fitur_df['Null Pctg'] = null_summary_fitur_df['Null Pctg'].map('{:.2f}%'.format)

    print("Summary Null Values :")
    print(null_summary_fitur_df.to_string())

=== distress_ppk ===
Summary Null Values :
                                      Null Count Null Pctg
Intang_TA                                   1579    61.13%
Percent Owned - All Institutions (%)        1280    49.55%
Parent Percent Owned (%)                    1137    44.02%
Percent Owned - Insiders (%)                 697    26.98%
Prepaid_CA                                   447    17.31%
Inventory_CA                                 357    13.82%
NetDebt_EBITDA                               300    11.61%
EBITDA_TA                                    291    11.27%
Altman_X3_EBIT_TA                            288    11.15%
Sales_growth                                 213     8.25%
CR                                           207     8.01%
QR                                           205     7.94%
TA_growth                                    202     7.82%
Equity_growth                                202     7.82%
NI_growth                                    202     7.82%
CFO_growth                                   202     7.82%
GrossMargin                                   52     2.01%
Age_When_IPO                                  11     0.43%
Debt_Equity                                    9     0.35%
Debt_TA                                        9     0.35%
PPE_TA                                         6     0.23%
OpMargin                                       5     0.19%
Altman_X5_SalesTA_AssetTurnover                5     0.19%
WC_Sales                                       5     0.19%
log_Sales                                      5     0.19%
NetMargin                                      5     0.19%
Sales_per_share                                5     0.19%
CFO_Sales                                      5     0.19%
Altman_X4_MVE_TL                               2     0.08%
PB                                             2     0.08%
log_MktCap                                     2     0.08%
CFO_per_share                                  0     0.00%
CFO_TA                                         0     0.00%
EPS_proxy                                      0     0.00%
NetCash_TA                                     0     0.00%
CFO_TL                                         0     0.00%

=== distress_neg_equity ===
Summary Null Values :
                                      Null Count Null Pctg
Intang_TA                                   5348    83.52%
Percent Owned - All Institutions (%)        2773    43.31%
Parent Percent Owned (%)                    2638    41.20%
Percent Owned - Insiders (%)                1947    30.41%
Prepaid_CA                                  1109    17.32%
Inventory_CA                                 941    14.70%
NetDebt_EBITDA                               923    14.42%
EBITDA_TA                                    903    14.10%
Altman_X3_EBIT_TA                            896    13.99%
Sales_growth                                 720    11.24%
CFO_growth                                   704    10.99%
NI_growth                                    700    10.93%
Equity_growth                                700    10.93%
TA_growth                                    700    10.93%
CR                                           567     8.86%
QR                                           565     8.82%
GrossMargin                                  152     2.37%
Altman_X4_MVE_TL                              21     0.33%
Debt_Equity                                   21     0.33%
Debt_TA                                       21     0.33%
PB                                            21     0.33%
log_MktCap                                    21     0.33%
Age_When_IPO                                  15     0.23%
CFO_Sales                                     15     0.23%
Sales_per_share                               14     0.22%
PPE_TA                                        14     0.22%
WC_Sales                                      13     0.20%
Altman_X5_SalesTA_AssetTurnover               13     0.20%
OpMargin                                      13     0.20%
NetMargin                                     13     0.20%
log_Sales                                     13     0.20%
CFO_per_share                                  3     0.05%
CFO_TA                                         2     0.03%
NetCash_TA                                     2     0.03%
CFO_TL                                         2     0.03%
EPS_proxy                                      1     0.02%

=== distress_consecutive_loss ===
Summary Null Values :
                                      Null Count Null Pctg
Intang_TA                                   4367    84.63%
Percent Owned - All Institutions (%)        2082    40.35%
Parent Percent Owned (%)                    1980    38.37%
Percent Owned - Insiders (%)                1540    29.84%
Prepaid_CA                                   809    15.68%
NetDebt_EBITDA                               798    15.47%
EBITDA_TA                                    785    15.21%
Altman_X3_EBIT_TA                            785    15.21%
Inventory_CA                                 778    15.08%
Sales_growth                                 722    13.99%
CFO_growth                                   714    13.84%
NI_growth                                    712    13.80%
Equity_growth                                712    13.80%
TA_growth                                    712    13.80%
CR                                           520    10.08%
QR                                           518    10.04%
GrossMargin                                  110     2.13%
Altman_X4_MVE_TL                              17     0.33%
log_MktCap                                    17     0.33%
PB                                            17     0.33%
Age_When_IPO                                  15     0.29%
Debt_TA                                       14     0.27%
Debt_Equity                                   14     0.27%
CFO_Sales                                     10     0.19%
Sales_per_share                               10     0.19%
OpMargin                                       9     0.17%
Altman_X5_SalesTA_AssetTurnover                9     0.17%
WC_Sales                                       9     0.17%
log_Sales                                      9     0.17%
NetMargin                                      9     0.17%
PPE_TA                                         6     0.12%
CFO_per_share                                  2     0.04%
NetCash_TA                                     1     0.02%
EPS_proxy                                      1     0.02%
CFO_TA                                         1     0.02%
CFO_TL                                         1     0.02%
null_summary_fitur_df = pd.DataFrame({
    'Null Count': df_fitur[features_with_null].isnull().sum(),
    'Null Pctg': (df_fitur[features_with_null].isnull().sum() / len(df_fitur)) * 100
}).sort_values(by='Null Count', ascending=False)

null_summary_fitur_df['Null Pctg'] = null_summary_fitur_df['Null Pctg'].map('{:.2f}%'.format)

print("Summary Null Values di df_fitur:")
print(null_summary_fitur_df.to_string())
Summary Null Values di df_fitur:
                                      Null Count Null Pctg
Intang_TA                                   6726    79.23%
Percent Owned - All Institutions (%)        3947    46.50%
Parent Percent Owned (%)                    3641    42.89%
Percent Owned - Insiders (%)                2551    30.05%
Prepaid_CA                                  1462    17.22%
Inventory_CA                                1206    14.21%
NetDebt_EBITDA                              1126    13.26%
EBITDA_TA                                   1106    13.03%
Altman_X3_EBIT_TA                           1099    12.95%
Sales_growth                                 902    10.63%
CFO_growth                                   857    10.10%
NI_growth                                    853    10.05%
Equity_growth                                853    10.05%
TA_growth                                    853    10.05%
CR                                           706     8.32%
QR                                           704     8.29%
GrossMargin                                  198     2.33%
CFO_Sales                                     33     0.39%
Sales_per_share                               32     0.38%
Altman_X5_SalesTA_AssetTurnover               31     0.37%
log_Sales                                     31     0.37%
WC_Sales                                      31     0.37%
OpMargin                                      31     0.37%
NetMargin                                     31     0.37%
Altman_X4_MVE_TL                              25     0.29%
PB                                            25     0.29%
log_MktCap                                    25     0.29%
Debt_Equity                                   21     0.25%
Debt_TA                                       21     0.25%
Age_When_IPO                                  21     0.25%
PPE_TA                                        19     0.22%
CFO_per_share                                  3     0.04%
CFO_TA                                         2     0.02%
NetCash_TA                                     2     0.02%
CFO_TL                                         2     0.02%
EPS_proxy                                      1     0.01%

Jumlah Baris tiap Kombinasi Jumlah Null

null_counts_per_row_fitur = df_fitur.isnull().sum(axis=1)
null_counts_grouped_fitur = null_counts_per_row_fitur.value_counts().sort_index(ascending=False)

print("Jumlah baris berdasarkan jumlah nilai null (df_fitur, diurutkan dari yang terbanyak):")
print(null_counts_grouped_fitur.to_string())
Jumlah baris berdasarkan jumlah nilai null (df_fitur, diurutkan dari yang terbanyak):
23       1
21       1
20       1
19       5
18       4
17      22
16      28
15      37
14      62
13     115
12     264
11     433
10     586
9      391
8      541
7      960
6     1778
5     1819
4      973
3      391
2       68
1        9

Proporsi Industri pada Null tiap Kolom

# Pastikan variabel df_fitur dan list features_with_null telah didefinisikan sebelumnya
total_baris_df = len(df_fitur)
n_features = len(features_with_null)

if n_features == 0:
    print("Tidak ada fitur yang memiliki nilai null untuk divisualisasikan.")
else:
    cols = 4
    rows = math.ceil(n_features / cols)

    fig, axes = plt.subplots(rows, cols, figsize=(6 * cols, 4.5 * rows))
    axes = axes.flatten()

    # --- 1. PEMETAAN WARNA KONSISTEN ---
    # Mengambil semua kategori unik Industry Group yang ada di df_fitur
    semua_industri = df_fitur['Industry Group'].dropna().unique()

    # Membuat dictionary warna menggunakan palet 'tab20' (mendukung hingga 20 warna berbeda)
    cmap = plt.get_cmap('tab20')
    color_mapping = {}
    for idx, industri in enumerate(semua_industri):
        color_mapping[industri] = cmap(idx % cmap.N)

    # Menetapkan warna abu-abu terang secara absolut untuk kategori 'Other'
    color_mapping['Other'] = '#D3D3D3'

    for i, feature in enumerate(features_with_null):
        ax = axes[i]

        df_null = df_fitur[df_fitur[feature].isnull()]
        total_null = len(df_null)
        pct_dari_total = (total_null / total_baris_df) * 100

        jumlah_sebaran = df_null['Industry Group'].value_counts()
        persentase_sebaran = (jumlah_sebaran / total_null) * 100

        mask_utama = persentase_sebaran >= 10
        mask_other = persentase_sebaran < 10

        final_counts = jumlah_sebaran[mask_utama].copy()

        other_count = jumlah_sebaran[mask_other].sum()
        if other_count > 0:
            final_counts['Other'] = other_count

        custom_labels = []
        custom_colors = []

        for idx, val in final_counts.items():
            pct = (val / total_null) * 100

            # Memotong teks yang panjang
            nama_grup_wrapped = textwrap.fill(str(idx), width=15)
            custom_labels.append(f"{nama_grup_wrapped}\n{val} ({pct:.1f}%)")

            # Memanggil warna dari dictionary menggunakan nama industri yang asli
            custom_colors.append(color_mapping[idx])

        # Memasukkan custom_colors ke dalam parameter colors
        ax.pie(final_counts, labels=custom_labels, colors=custom_colors, startangle=90,
               wedgeprops={'edgecolor': 'white', 'linewidth': 1},
               textprops={'fontsize': 9})

        ax.set_title(f"{feature}\nTotal Null: {total_null} ({pct_dari_total:.2f}% dari total)",
                     fontsize=11, fontweight='bold')

        # --- 2. GARIS PEMBATAS HITAM TIPIS ---
        # Memaksa frame kotak untuk tampil di sekitar pie chart
        ax.set_frame_on(True)
        # Menghapus penanda angka (ticks) agar kotak tetap bersih
        ax.set_xticks([])
        ax.set_yticks([])
        # Mengatur warna dan ketebalan garis tepi kotak menjadi hitam tipis
        for spine in ax.spines.values():
            spine.set_edgecolor('black')
            spine.set_linewidth(0.5)
            spine.set_visible(True)

    # Menghapus subplot yang tidak terpakai
    for j in range(i + 1, len(axes)):
        fig.delaxes(axes[j])

    # Merapatkan jarak antar plot
    plt.tight_layout(h_pad=0.5, w_pad=1.0)
    plt.show()

for feature in features_with_null:
    # Mengambil subset data di mana kolom fitur tersebut bernilai null
    df_null = df_fitur[df_fitur[feature].isnull()]

    # Menghitung total baris yang null pada fitur tersebut
    total_null = len(df_null)

    print(f"=== Fitur: {feature} ===")
    print(f"Total Baris Null : {total_null}")

    if total_null > 0:
        # Menghitung jumlah null di tiap Industry Group
        jumlah_sebaran = df_null['Industry Group'].value_counts()

        # Menghitung persentase sebaran null di tiap Industry Group
        persentase_sebaran = df_null['Industry Group'].value_counts(normalize=True) * 100

        # Menggabungkan hasil ke dalam satu DataFrame agar tampilannya rapi
        df_sebaran = pd.DataFrame({
            'Jumlah Null': jumlah_sebaran,
            'Persentase (%)': persentase_sebaran
        })

        # Membulatkan persentase menjadi 2 angka di belakang koma
        df_sebaran['Persentase (%)'] = df_sebaran['Persentase (%)'].round(2)

        print(df_sebaran)
    else:
        print("Tidak ada nilai null pada fitur ini.")

    print("\n" + "="*40 + "\n")
=== Fitur: Parent Percent Owned (%) ===
Total Baris Null : 3641
                                                  Jumlah Null  Persentase (%)
Industry Group                                                               
Real Estate Management and Development                    531       14.580000
Materials                                                 449       12.330000
Capital Goods                                             399       10.960000
Food, Beverage and Tobacco                                378       10.380000
Energy                                                    331        9.090000
Transportation                                            255        7.000000
Consumer Services                                         191        5.250000
Financial Services                                        182        5.000000
Consumer Discretionary Distribution and Retail            139        3.820000
Consumer Durables and Apparel                             133        3.650000
Media and Entertainment                                    92        2.530000
Technology Hardware and Equipment                          82        2.250000
Commercial and Professional Services                       74        2.030000
Banks                                                      70        1.920000
Telecommunication Services                                 65        1.790000
Software and Services                                      59        1.620000
Insurance                                                  51        1.400000
Consumer Staples Distribution and Retail                   39        1.070000
Utilities                                                  32        0.880000
Health Care Equipment and Services                         28        0.770000
Automobiles and Components                                 22        0.600000
Pharmaceuticals, Biotechnology and Life Sciences           21        0.580000
Household and Personal Products                            18        0.490000

========================================

=== Fitur: Percent Owned - All Institutions (%) ===
Total Baris Null : 3947
                                                  Jumlah Null  Persentase (%)
Industry Group                                                               
Materials                                                 467       11.830000
Real Estate Management and Development                    456       11.550000
Capital Goods                                             437       11.070000
Food, Beverage and Tobacco                                295        7.470000
Transportation                                            266        6.740000
Consumer Durables and Apparel                             251        6.360000
Consumer Services                                         219        5.550000
Energy                                                    201        5.090000
Banks                                                     198        5.020000
Financial Services                                        167        4.230000
Insurance                                                 134        3.390000
Telecommunication Services                                112        2.840000
Media and Entertainment                                   110        2.790000
Consumer Discretionary Distribution and Retail            108        2.740000
Consumer Staples Distribution and Retail                   96        2.430000
Commercial and Professional Services                       68        1.720000
Health Care Equipment and Services                         68        1.720000
Technology Hardware and Equipment                          62        1.570000
Pharmaceuticals, Biotechnology and Life Sciences           61        1.550000
Automobiles and Components                                 60        1.520000
Household and Personal Products                            55        1.390000
Software and Services                                      37        0.940000
Utilities                                                  19        0.480000

========================================

=== Fitur: Percent Owned - Insiders (%) ===
Total Baris Null : 2551
                                                  Jumlah Null  Persentase (%)
Industry Group                                                               
Capital Goods                                             320       12.540000
Real Estate Management and Development                    267       10.470000
Food, Beverage and Tobacco                                262       10.270000
Financial Services                                        210        8.230000
Banks                                                     189        7.410000
Materials                                                 170        6.660000
Energy                                                    161        6.310000
Consumer Durables and Apparel                             126        4.940000
Consumer Services                                         126        4.940000
Transportation                                            103        4.040000
Insurance                                                  95        3.720000
Pharmaceuticals, Biotechnology and Life Sciences           85        3.330000
Consumer Discretionary Distribution and Retail             80        3.140000
Commercial and Professional Services                       72        2.820000
Telecommunication Services                                 52        2.040000
Consumer Staples Distribution and Retail                   51        2.000000
Automobiles and Components                                 41        1.610000
Health Care Equipment and Services                         41        1.610000
Software and Services                                      41        1.610000
Media and Entertainment                                    33        1.290000
Household and Personal Products                            19        0.740000
Technology Hardware and Equipment                           7        0.270000

========================================

=== Fitur: CR ===
Total Baris Null : 706
                                        Jumlah Null  Persentase (%)
Industry Group                                                     
Banks                                           635       89.940000
Insurance                                        34        4.820000
Financial Services                               30        4.250000
Real Estate Management and Development            3        0.420000
Transportation                                    2        0.280000
Capital Goods                                     2        0.280000

========================================

=== Fitur: QR ===
Total Baris Null : 704
                                        Jumlah Null  Persentase (%)
Industry Group                                                     
Banks                                           635       90.200000
Insurance                                        34        4.830000
Financial Services                               30        4.260000
Transportation                                    2        0.280000
Capital Goods                                     2        0.280000
Real Estate Management and Development            1        0.140000

========================================

=== Fitur: Age_When_IPO ===
Total Baris Null : 21
                    Jumlah Null  Persentase (%)
Industry Group                                 
Financial Services           15       71.430000
Banks                         6       28.570000

========================================

=== Fitur: WC_Sales ===
Total Baris Null : 31
                                        Jumlah Null  Persentase (%)
Industry Group                                                     
Energy                                            6       19.350000
Materials                                         6       19.350000
Transportation                                    5       16.130000
Utilities                                         3        9.680000
Real Estate Management and Development            3        9.680000
Capital Goods                                     2        6.450000
Consumer Services                                 2        6.450000
Commercial and Professional Services              2        6.450000
Telecommunication Services                        1        3.230000
Financial Services                                1        3.230000

========================================

=== Fitur: Inventory_CA ===
Total Baris Null : 1206
                                                Jumlah Null  Persentase (%)
Industry Group                                                             
Financial Services                                      381       31.590000
Insurance                                               253       20.980000
Transportation                                          112        9.290000
Banks                                                    80        6.630000
Telecommunication Services                               74        6.140000
Real Estate Management and Development                   63        5.220000
Capital Goods                                            63        5.220000
Energy                                                   49        4.060000
Consumer Services                                        36        2.990000
Media and Entertainment                                  22        1.820000
Software and Services                                    21        1.740000
Commercial and Professional Services                     16        1.330000
Utilities                                                16        1.330000
Materials                                                11        0.910000
Food, Beverage and Tobacco                                5        0.410000
Household and Personal Products                           2        0.170000
Consumer Discretionary Distribution and Retail            2        0.170000

========================================

=== Fitur: Prepaid_CA ===
Total Baris Null : 1462
                                                  Jumlah Null  Persentase (%)
Industry Group                                                               
Financial Services                                        391       26.740000
Real Estate Management and Development                    277       18.950000
Energy                                                    129        8.820000
Capital Goods                                             114        7.800000
Materials                                                  95        6.500000
Food, Beverage and Tobacco                                 73        4.990000
Transportation                                             73        4.990000
Consumer Services                                          50        3.420000
Commercial and Professional Services                       33        2.260000
Software and Services                                      28        1.920000
Consumer Discretionary Distribution and Retail             25        1.710000
Insurance                                                  21        1.440000
Automobiles and Components                                 20        1.370000
Telecommunication Services                                 20        1.370000
Banks                                                      20        1.370000
Consumer Durables and Apparel                              19        1.300000
Consumer Staples Distribution and Retail                   18        1.230000
Technology Hardware and Equipment                          17        1.160000
Utilities                                                  15        1.030000
Media and Entertainment                                    14        0.960000
Pharmaceuticals, Biotechnology and Life Sciences            8        0.550000
Household and Personal Products                             1        0.070000
Health Care Equipment and Services                          1        0.070000

========================================

=== Fitur: Debt_TA ===
Total Baris Null : 21
                                 Jumlah Null  Persentase (%)
Industry Group                                              
Materials                                  9       42.860000
Telecommunication Services                 7       33.330000
Household and Personal Products            5       23.810000

========================================

=== Fitur: Debt_Equity ===
Total Baris Null : 21
                                 Jumlah Null  Persentase (%)
Industry Group                                              
Materials                                  9       42.860000
Telecommunication Services                 7       33.330000
Household and Personal Products            5       23.810000

========================================

=== Fitur: NetDebt_EBITDA ===
Total Baris Null : 1126
                                 Jumlah Null  Persentase (%)
Industry Group                                              
Banks                                    635       56.390000
Financial Services                       375       33.300000
Insurance                                 34        3.020000
Energy                                    18        1.600000
Software and Services                     17        1.510000
Capital Goods                             15        1.330000
Consumer Services                         10        0.890000
Materials                                  9        0.800000
Telecommunication Services                 7        0.620000
Household and Personal Products            5        0.440000
Transportation                             1        0.090000

========================================

=== Fitur: PPE_TA ===
Total Baris Null : 19
                            Jumlah Null  Persentase (%)
Industry Group                                         
Financial Services                    9       47.370000
Capital Goods                         3       15.790000
Transportation                        3       15.790000
Energy                                3       15.790000
Telecommunication Services            1        5.260000

========================================

=== Fitur: Intang_TA ===
Total Baris Null : 6726
                                                  Jumlah Null  Persentase (%)
Industry Group                                                               
Materials                                                 849       12.620000
Real Estate Management and Development                    752       11.180000
Food, Beverage and Tobacco                                660        9.810000
Capital Goods                                             645        9.590000
Energy                                                    594        8.830000
Transportation                                            470        6.990000
Banks                                                     417        6.200000
Financial Services                                        329        4.890000
Consumer Services                                         315        4.680000
Consumer Durables and Apparel                             261        3.880000
Consumer Discretionary Distribution and Retail            227        3.370000
Insurance                                                 206        3.060000
Media and Entertainment                                   155        2.300000
Telecommunication Services                                137        2.040000
Consumer Staples Distribution and Retail                  115        1.710000
Commercial and Professional Services                      100        1.490000
Automobiles and Components                                 99        1.470000
Health Care Equipment and Services                         92        1.370000
Household and Personal Products                            83        1.230000
Pharmaceuticals, Biotechnology and Life Sciences           77        1.140000
Technology Hardware and Equipment                          70        1.040000
Utilities                                                  54        0.800000
Software and Services                                      19        0.280000

========================================

=== Fitur: Altman_X3_EBIT_TA ===
Total Baris Null : 1099
                            Jumlah Null  Persentase (%)
Industry Group                                         
Banks                               635       57.780000
Financial Services                  375       34.120000
Insurance                            34        3.090000
Software and Services                17        1.550000
Energy                               17        1.550000
Capital Goods                        10        0.910000
Consumer Services                    10        0.910000
Telecommunication Services            1        0.090000

========================================

=== Fitur: Altman_X4_MVE_TL ===
Total Baris Null : 25
                            Jumlah Null  Persentase (%)
Industry Group                                         
Banks                                 6       24.000000
Consumer Services                     6       24.000000
Materials                             4       16.000000
Energy                                3       12.000000
Food, Beverage and Tobacco            2        8.000000
Transportation                        2        8.000000
Financial Services                    1        4.000000
Capital Goods                         1        4.000000

========================================

=== Fitur: Altman_X5_SalesTA_AssetTurnover ===
Total Baris Null : 31
                                        Jumlah Null  Persentase (%)
Industry Group                                                     
Energy                                            6       19.350000
Materials                                         6       19.350000
Transportation                                    5       16.130000
Utilities                                         3        9.680000
Real Estate Management and Development            3        9.680000
Capital Goods                                     2        6.450000
Consumer Services                                 2        6.450000
Commercial and Professional Services              2        6.450000
Telecommunication Services                        1        3.230000
Financial Services                                1        3.230000

========================================

=== Fitur: EBITDA_TA ===
Total Baris Null : 1106
                            Jumlah Null  Persentase (%)
Industry Group                                         
Banks                               635       57.410000
Financial Services                  375       33.910000
Insurance                            34        3.070000
Energy                               18        1.630000
Software and Services                17        1.540000
Capital Goods                        15        1.360000
Consumer Services                    10        0.900000
Telecommunication Services            1        0.090000
Transportation                        1        0.090000

========================================

=== Fitur: GrossMargin ===
Total Baris Null : 198
                                        Jumlah Null  Persentase (%)
Industry Group                                                     
Banks                                            87       43.940000
Financial Services                               43       21.720000
Materials                                        14        7.070000
Commercial and Professional Services             13        6.570000
Energy                                           12        6.060000
Capital Goods                                    11        5.560000
Transportation                                    5        2.530000
Consumer Services                                 3        1.520000
Real Estate Management and Development            3        1.520000
Utilities                                         3        1.520000
Software and Services                             1        0.510000
Telecommunication Services                        1        0.510000
Food, Beverage and Tobacco                        1        0.510000
Insurance                                         1        0.510000

========================================

=== Fitur: OpMargin ===
Total Baris Null : 31
                                        Jumlah Null  Persentase (%)
Industry Group                                                     
Energy                                            6       19.350000
Materials                                         6       19.350000
Transportation                                    5       16.130000
Utilities                                         3        9.680000
Real Estate Management and Development            3        9.680000
Capital Goods                                     2        6.450000
Consumer Services                                 2        6.450000
Commercial and Professional Services              2        6.450000
Telecommunication Services                        1        3.230000
Financial Services                                1        3.230000

========================================

=== Fitur: NetMargin ===
Total Baris Null : 31
                                        Jumlah Null  Persentase (%)
Industry Group                                                     
Energy                                            6       19.350000
Materials                                         6       19.350000
Transportation                                    5       16.130000
Utilities                                         3        9.680000
Real Estate Management and Development            3        9.680000
Capital Goods                                     2        6.450000
Consumer Services                                 2        6.450000
Commercial and Professional Services              2        6.450000
Telecommunication Services                        1        3.230000
Financial Services                                1        3.230000

========================================

=== Fitur: CFO_TA ===
Total Baris Null : 2
                Jumlah Null  Persentase (%)
Industry Group                             
Energy                    2      100.000000

========================================

=== Fitur: CFO_TL ===
Total Baris Null : 2
                Jumlah Null  Persentase (%)
Industry Group                             
Energy                    2      100.000000

========================================

=== Fitur: CFO_Sales ===
Total Baris Null : 33
                                        Jumlah Null  Persentase (%)
Industry Group                                                     
Energy                                            8       24.240000
Materials                                         6       18.180000
Transportation                                    5       15.150000
Utilities                                         3        9.090000
Real Estate Management and Development            3        9.090000
Capital Goods                                     2        6.060000
Consumer Services                                 2        6.060000
Commercial and Professional Services              2        6.060000
Telecommunication Services                        1        3.030000
Financial Services                                1        3.030000

========================================

=== Fitur: NetCash_TA ===
Total Baris Null : 2
                Jumlah Null  Persentase (%)
Industry Group                             
Energy                    2      100.000000

========================================

=== Fitur: EPS_proxy ===
Total Baris Null : 1
                Jumlah Null  Persentase (%)
Industry Group                             
Banks                     1      100.000000

========================================

=== Fitur: Sales_per_share ===
Total Baris Null : 32
                                        Jumlah Null  Persentase (%)
Industry Group                                                     
Materials                                         6       18.750000
Energy                                            6       18.750000
Transportation                                    5       15.620000
Real Estate Management and Development            3        9.380000
Utilities                                         3        9.380000
Consumer Services                                 2        6.250000
Capital Goods                                     2        6.250000
Commercial and Professional Services              2        6.250000
Financial Services                                1        3.120000
Telecommunication Services                        1        3.120000
Banks                                             1        3.120000

========================================

=== Fitur: CFO_per_share ===
Total Baris Null : 3
                Jumlah Null  Persentase (%)
Industry Group                             
Energy                    2       66.670000
Banks                     1       33.330000

========================================

=== Fitur: log_Sales ===
Total Baris Null : 31
                                        Jumlah Null  Persentase (%)
Industry Group                                                     
Energy                                            6       19.350000
Materials                                         6       19.350000
Transportation                                    5       16.130000
Utilities                                         3        9.680000
Real Estate Management and Development            3        9.680000
Capital Goods                                     2        6.450000
Consumer Services                                 2        6.450000
Commercial and Professional Services              2        6.450000
Telecommunication Services                        1        3.230000
Financial Services                                1        3.230000

========================================

=== Fitur: log_MktCap ===
Total Baris Null : 25
                            Jumlah Null  Persentase (%)
Industry Group                                         
Banks                                 6       24.000000
Consumer Services                     6       24.000000
Materials                             4       16.000000
Energy                                3       12.000000
Food, Beverage and Tobacco            2        8.000000
Transportation                        2        8.000000
Financial Services                    1        4.000000
Capital Goods                         1        4.000000

========================================

=== Fitur: PB ===
Total Baris Null : 25
                            Jumlah Null  Persentase (%)
Industry Group                                         
Banks                                 6       24.000000
Consumer Services                     6       24.000000
Materials                             4       16.000000
Energy                                3       12.000000
Food, Beverage and Tobacco            2        8.000000
Transportation                        2        8.000000
Financial Services                    1        4.000000
Capital Goods                         1        4.000000

========================================

=== Fitur: Sales_growth ===
Total Baris Null : 902
                                                  Jumlah Null  Persentase (%)
Industry Group                                                               
Materials                                                 100       11.090000
Real Estate Management and Development                     93       10.310000
Food, Beverage and Tobacco                                 92       10.200000
Capital Goods                                              88        9.760000
Transportation                                             68        7.540000
Energy                                                     65        7.210000
Banks                                                      48        5.320000
Consumer Services                                          42        4.660000
Consumer Discretionary Distribution and Retail             37        4.100000
Financial Services                                         34        3.770000
Media and Entertainment                                    30        3.330000
Consumer Durables and Apparel                              27        2.990000
Health Care Equipment and Services                         24        2.660000
Insurance                                                  21        2.330000
Telecommunication Services                                 20        2.220000
Software and Services                                      19        2.110000
Commercial and Professional Services                       18        2.000000
Technology Hardware and Equipment                          16        1.770000
Consumer Staples Distribution and Retail                   15        1.660000
Utilities                                                  13        1.440000
Household and Personal Products                            12        1.330000
Automobiles and Components                                 11        1.220000
Pharmaceuticals, Biotechnology and Life Sciences            9        1.000000

========================================

=== Fitur: NI_growth ===
Total Baris Null : 853
                                                  Jumlah Null  Persentase (%)
Industry Group                                                               
Food, Beverage and Tobacco                                 92       10.790000
Materials                                                  92       10.790000
Real Estate Management and Development                     88       10.320000
Capital Goods                                              85        9.960000
Transportation                                             59        6.920000
Energy                                                     56        6.570000
Banks                                                      48        5.630000
Consumer Services                                          38        4.450000
Consumer Discretionary Distribution and Retail             37        4.340000
Financial Services                                         32        3.750000
Media and Entertainment                                    30        3.520000
Consumer Durables and Apparel                              27        3.170000
Health Care Equipment and Services                         24        2.810000
Insurance                                                  21        2.460000
Software and Services                                      19        2.230000
Telecommunication Services                                 18        2.110000
Technology Hardware and Equipment                          16        1.880000
Commercial and Professional Services                       15        1.760000
Consumer Staples Distribution and Retail                   15        1.760000
Household and Personal Products                            12        1.410000
Automobiles and Components                                 11        1.290000
Utilities                                                   9        1.060000
Pharmaceuticals, Biotechnology and Life Sciences            9        1.060000

========================================

=== Fitur: TA_growth ===
Total Baris Null : 853
                                                  Jumlah Null  Persentase (%)
Industry Group                                                               
Food, Beverage and Tobacco                                 92       10.790000
Materials                                                  92       10.790000
Real Estate Management and Development                     88       10.320000
Capital Goods                                              85        9.960000
Transportation                                             59        6.920000
Energy                                                     56        6.570000
Banks                                                      48        5.630000
Consumer Services                                          38        4.450000
Consumer Discretionary Distribution and Retail             37        4.340000
Financial Services                                         32        3.750000
Media and Entertainment                                    30        3.520000
Consumer Durables and Apparel                              27        3.170000
Health Care Equipment and Services                         24        2.810000
Insurance                                                  21        2.460000
Software and Services                                      19        2.230000
Telecommunication Services                                 18        2.110000
Technology Hardware and Equipment                          16        1.880000
Commercial and Professional Services                       15        1.760000
Consumer Staples Distribution and Retail                   15        1.760000
Household and Personal Products                            12        1.410000
Automobiles and Components                                 11        1.290000
Utilities                                                   9        1.060000
Pharmaceuticals, Biotechnology and Life Sciences            9        1.060000

========================================

=== Fitur: Equity_growth ===
Total Baris Null : 853
                                                  Jumlah Null  Persentase (%)
Industry Group                                                               
Food, Beverage and Tobacco                                 92       10.790000
Materials                                                  92       10.790000
Real Estate Management and Development                     88       10.320000
Capital Goods                                              85        9.960000
Transportation                                             59        6.920000
Energy                                                     56        6.570000
Banks                                                      48        5.630000
Consumer Services                                          38        4.450000
Consumer Discretionary Distribution and Retail             37        4.340000
Financial Services                                         32        3.750000
Media and Entertainment                                    30        3.520000
Consumer Durables and Apparel                              27        3.170000
Health Care Equipment and Services                         24        2.810000
Insurance                                                  21        2.460000
Software and Services                                      19        2.230000
Telecommunication Services                                 18        2.110000
Technology Hardware and Equipment                          16        1.880000
Commercial and Professional Services                       15        1.760000
Consumer Staples Distribution and Retail                   15        1.760000
Household and Personal Products                            12        1.410000
Automobiles and Components                                 11        1.290000
Utilities                                                   9        1.060000
Pharmaceuticals, Biotechnology and Life Sciences            9        1.060000

========================================

=== Fitur: CFO_growth ===
Total Baris Null : 857
                                                  Jumlah Null  Persentase (%)
Industry Group                                                               
Food, Beverage and Tobacco                                 92       10.740000
Materials                                                  92       10.740000
Real Estate Management and Development                     88       10.270000
Capital Goods                                              85        9.920000
Energy                                                     60        7.000000
Transportation                                             59        6.880000
Banks                                                      48        5.600000
Consumer Services                                          38        4.430000
Consumer Discretionary Distribution and Retail             37        4.320000
Financial Services                                         32        3.730000
Media and Entertainment                                    30        3.500000
Consumer Durables and Apparel                              27        3.150000
Health Care Equipment and Services                         24        2.800000
Insurance                                                  21        2.450000
Software and Services                                      19        2.220000
Telecommunication Services                                 18        2.100000
Technology Hardware and Equipment                          16        1.870000
Commercial and Professional Services                       15        1.750000
Consumer Staples Distribution and Retail                   15        1.750000
Household and Personal Products                            12        1.400000
Automobiles and Components                                 11        1.280000
Utilities                                                   9        1.050000
Pharmaceuticals, Biotechnology and Life Sciences            9        1.050000

========================================

Sebaran Null tiap Tahun Data

total_baris_df = len(df_fitur)
n_features = len(features_with_null)

# Mendefinisikan rentang tahun unik secara eksplisit (2008 hingga 2024)
all_years = list(range(2008, 2025))

if n_features == 0:
    print("Tidak ada fitur yang memiliki nilai null untuk divisualisasikan.")
else:
    cols = 4
    rows = math.ceil(n_features / cols)

    fig, axes = plt.subplots(rows, cols, figsize=(7 * cols, 6 * rows))
    axes = axes.flatten()

    for i, feature in enumerate(features_with_null):
        ax = axes[i]

        df_null = df_fitur[df_fitur[feature].isnull()]
        total_null = len(df_null)
        pct_dari_total = (total_null / total_baris_df) * 100

        # Menghitung jumlah null dan melakukan reindex agar mencakup semua tahun 2008-2024
        jumlah_sebaran = df_null['Year'].value_counts().reindex(all_years, fill_value=0)

        # Menghitung persentase terhadap total null pada fitur tersebut
        # Jika total_null == 0, diabaikan karena fitur tersebut tidak akan masuk ke list
        persentase_sebaran = (jumlah_sebaran / total_null) * 100

        years = jumlah_sebaran.index.astype(str)
        counts = jumlah_sebaran.values
        pcts = persentase_sebaran.values

        bars = ax.bar(years, counts, color='steelblue')

        # Menambahkan teks anotasi secara vertikal di atas setiap batang
        for bar, count, pct in zip(bars, counts, pcts):
            # Teks anotasi hanya ditambahkan jika jumlah null > 0 agar grafik tetap bersih
            if count > 0:
                height = bar.get_height()
                ax.annotate(f'{int(count)} ({pct:.1f}%)',
                            xy=(bar.get_x() + bar.get_width() / 2, height),
                            xytext=(0, 5),
                            textcoords="offset points",
                            ha='center', va='bottom',
                            fontsize=9, rotation=90)

        ax.set_title(f"{feature}\nTotal Null: {total_null} ({pct_dari_total:.2f}% dari total)",
                     fontsize=11, fontweight='bold')
        ax.set_xlabel('Tahun', fontsize=10)
        ax.set_ylabel('Jumlah Null', fontsize=10)

        ax.set_xticks(range(len(years)))
        ax.set_xticklabels(years, rotation=90)

        if max(counts) > 0:
            ax.set_ylim(0, max(counts) * 1.6)

    # Menghapus subplot (axes) yang tidak terpakai
    for j in range(i + 1, len(axes)):
        fig.delaxes(axes[j])

    plt.tight_layout()
    plt.show()

for feature in features_with_null:
    # Mengambil subset data di mana kolom fitur tersebut bernilai null
    df_null = df_fitur[df_fitur[feature].isnull()]

    # Menghitung total baris yang null pada fitur tersebut
    total_null = len(df_null)

    print(f"=== Fitur: {feature} ===")
    print(f"Total Baris Null : {total_null}")

    if total_null > 0:
        # Menghitung jumlah null di tiap Year
        jumlah_sebaran = df_null['Year'].value_counts()

        # Menghitung persentase sebaran null di tiap Year
        persentase_sebaran = df_null['Year'].value_counts(normalize=True) * 100

        # Menggabungkan hasil ke dalam satu DataFrame agar tampilannya rapi
        df_sebaran = pd.DataFrame({
            'Jumlah Null': jumlah_sebaran,
            'Persentase (%)': persentase_sebaran
        })

        # Membulatkan persentase menjadi 2 angka di belakang koma
        df_sebaran['Persentase (%)'] = df_sebaran['Persentase (%)'].round(2)

        print(df_sebaran.sort_index(ascending=False))
    else:
        print("Tidak ada nilai null pada fitur ini.")

    print("\n" + "="*40 + "\n")
=== Fitur: Parent Percent Owned (%) ===
Total Baris Null : 3641
      Jumlah Null  Persentase (%)
Year                             
2024          403       11.070000
2023          359        9.860000
2022          330        9.060000
2021          292        8.020000
2020          261        7.170000
2019          234        6.430000
2018          211        5.800000
2017          196        5.380000
2016          186        5.110000
2015          176        4.830000
2014          167        4.590000
2013          160        4.390000
2012          149        4.090000
2011          139        3.820000
2010          131        3.600000
2009          126        3.460000
2008          121        3.320000

========================================

=== Fitur: Percent Owned - All Institutions (%) ===
Total Baris Null : 3947
      Jumlah Null  Persentase (%)
Year                             
2024          460       11.650000
2023          405       10.260000
2022          367        9.300000
2021          327        8.280000
2020          286        7.250000
2019          243        6.160000
2018          213        5.400000
2017          200        5.070000
2016          196        4.970000
2015          189        4.790000
2014          176        4.460000
2013          168        4.260000
2012          156        3.950000
2011          145        3.670000
2010          142        3.600000
2009          140        3.550000
2008          134        3.390000

========================================

=== Fitur: Percent Owned - Insiders (%) ===
Total Baris Null : 2551
      Jumlah Null  Persentase (%)
Year                             
2024          205        8.040000
2023          196        7.680000
2022          193        7.570000
2021          187        7.330000
2020          178        6.980000
2019          170        6.660000
2018          159        6.230000
2017          149        5.840000
2016          146        5.720000
2015          142        5.570000
2014          133        5.210000
2013          128        5.020000
2012          121        4.740000
2011          116        4.550000
2010          113        4.430000
2009          109        4.270000
2008          106        4.160000

========================================

=== Fitur: CR ===
Total Baris Null : 706
      Jumlah Null  Persentase (%)
Year                             
2024           55        7.790000
2023           54        7.650000
2022           57        8.070000
2021           55        7.790000
2020           50        7.080000
2019           47        6.660000
2018           45        6.370000
2017           44        6.230000
2016           44        6.230000
2015           42        5.950000
2014           37        5.240000
2013           32        4.530000
2012           31        4.390000
2011           32        4.530000
2010           28        3.970000
2009           27        3.820000
2008           26        3.680000

========================================

=== Fitur: QR ===
Total Baris Null : 704
      Jumlah Null  Persentase (%)
Year                             
2024           55        7.810000
2023           54        7.670000
2022           56        7.950000
2021           54        7.670000
2020           50        7.100000
2019           47        6.680000
2018           45        6.390000
2017           44        6.250000
2016           44        6.250000
2015           42        5.970000
2014           37        5.260000
2013           32        4.550000
2012           31        4.400000
2011           32        4.550000
2010           28        3.980000
2009           27        3.840000
2008           26        3.690000

========================================

=== Fitur: Age_When_IPO ===
Total Baris Null : 21
      Jumlah Null  Persentase (%)
Year                             
2024            3       14.290000
2023            3       14.290000
2022            3       14.290000
2021            3       14.290000
2020            2        9.520000
2019            2        9.520000
2018            1        4.760000
2017            1        4.760000
2016            1        4.760000
2015            1        4.760000
2014            1        4.760000

========================================

=== Fitur: WC_Sales ===
Total Baris Null : 31
      Jumlah Null  Persentase (%)
Year                             
2024            1        3.230000
2023            1        3.230000
2022            2        6.450000
2021            1        3.230000
2020            2        6.450000
2019            2        6.450000
2018            2        6.450000
2017            2        6.450000
2016            4       12.900000
2015            2        6.450000
2014            1        3.230000
2013            1        3.230000
2012            1        3.230000
2011            2        6.450000
2010            4       12.900000
2009            2        6.450000
2008            1        3.230000

========================================

=== Fitur: Inventory_CA ===
Total Baris Null : 1206
      Jumlah Null  Persentase (%)
Year                             
2024          112        9.290000
2023          103        8.540000
2022          101        8.370000
2021           98        8.130000
2020           83        6.880000
2019           77        6.380000
2018           66        5.470000
2017           65        5.390000
2016           65        5.390000
2015           64        5.310000
2014           57        4.730000
2013           59        4.890000
2012           52        4.310000
2011           52        4.310000
2010           52        4.310000
2009           49        4.060000
2008           51        4.230000

========================================

=== Fitur: Prepaid_CA ===
Total Baris Null : 1462
      Jumlah Null  Persentase (%)
Year                             
2024          141        9.640000
2023          134        9.170000
2022          126        8.620000
2021          123        8.410000
2020          112        7.660000
2019           89        6.090000
2018           79        5.400000
2017           74        5.060000
2016           74        5.060000
2015           74        5.060000
2014           72        4.920000
2013           68        4.650000
2012           58        3.970000
2011           54        3.690000
2010           62        4.240000
2009           64        4.380000
2008           58        3.970000

========================================

=== Fitur: Debt_TA ===
Total Baris Null : 21
      Jumlah Null  Persentase (%)
Year                             
2022            3       14.290000
2021            3       14.290000
2020            3       14.290000
2019            2        9.520000
2018            1        4.760000
2017            1        4.760000
2016            1        4.760000
2015            1        4.760000
2014            2        9.520000
2013            2        9.520000
2009            1        4.760000
2008            1        4.760000

========================================

=== Fitur: Debt_Equity ===
Total Baris Null : 21
      Jumlah Null  Persentase (%)
Year                             
2022            3       14.290000
2021            3       14.290000
2020            3       14.290000
2019            2        9.520000
2018            1        4.760000
2017            1        4.760000
2016            1        4.760000
2015            1        4.760000
2014            2        9.520000
2013            2        9.520000
2009            1        4.760000
2008            1        4.760000

========================================

=== Fitur: NetDebt_EBITDA ===
Total Baris Null : 1126
      Jumlah Null  Persentase (%)
Year                             
2024           80        7.100000
2023           80        7.100000
2022           83        7.370000
2021           81        7.190000
2020           79        7.020000
2019           77        6.840000
2018           73        6.480000
2017           72        6.390000
2016           71        6.310000
2015           68        6.040000
2014           62        5.510000
2013           57        5.060000
2012           52        4.620000
2011           51        4.530000
2010           48        4.260000
2009           47        4.170000
2008           45        4.000000

========================================

=== Fitur: PPE_TA ===
Total Baris Null : 19
      Jumlah Null  Persentase (%)
Year                             
2024            3       15.790000
2023            1        5.260000
2022            1        5.260000
2021            2       10.530000
2020            2       10.530000
2019            1        5.260000
2018            1        5.260000
2017            2       10.530000
2016            2       10.530000
2015            1        5.260000
2014            1        5.260000
2011            1        5.260000
2010            1        5.260000

========================================

=== Fitur: Intang_TA ===
Total Baris Null : 6726
      Jumlah Null  Persentase (%)
Year                             
2024          493        7.330000
2023          452        6.720000
2022          440        6.540000
2021          414        6.160000
2020          393        5.840000
2019          367        5.460000
2018          341        5.070000
2017          470        6.990000
2016          458        6.810000
2015          439        6.530000
2014          414        6.160000
2013          392        5.830000
2012          367        5.460000
2011          344        5.110000
2010          325        4.830000
2009          315        4.680000
2008          302        4.490000

========================================

=== Fitur: Altman_X3_EBIT_TA ===
Total Baris Null : 1099
      Jumlah Null  Persentase (%)
Year                             
2024           80        7.280000
2023           80        7.280000
2022           79        7.190000
2021           77        7.010000
2020           75        6.820000
2019           74        6.730000
2018           71        6.460000
2017           71        6.460000
2016           69        6.280000
2015           68        6.190000
2014           60        5.460000
2013           55        5.000000
2012           52        4.730000
2011           50        4.550000
2010           48        4.370000
2009           46        4.190000
2008           44        4.000000

========================================

=== Fitur: Altman_X4_MVE_TL ===
Total Baris Null : 25
      Jumlah Null  Persentase (%)
Year                             
2024            1        4.000000
2022            1        4.000000
2021            1        4.000000
2020            1        4.000000
2018            1        4.000000
2017            1        4.000000
2016            1        4.000000
2015            1        4.000000
2014            2        8.000000
2013            3       12.000000
2012            1        4.000000
2011            1        4.000000
2010            3       12.000000
2009            2        8.000000
2008            5       20.000000

========================================

=== Fitur: Altman_X5_SalesTA_AssetTurnover ===
Total Baris Null : 31
      Jumlah Null  Persentase (%)
Year                             
2024            1        3.230000
2023            1        3.230000
2022            2        6.450000
2021            1        3.230000
2020            2        6.450000
2019            2        6.450000
2018            2        6.450000
2017            2        6.450000
2016            4       12.900000
2015            2        6.450000
2014            1        3.230000
2013            1        3.230000
2012            1        3.230000
2011            2        6.450000
2010            4       12.900000
2009            2        6.450000
2008            1        3.230000

========================================

=== Fitur: EBITDA_TA ===
Total Baris Null : 1106
      Jumlah Null  Persentase (%)
Year                             
2024           80        7.230000
2023           80        7.230000
2022           80        7.230000
2021           78        7.050000
2020           76        6.870000
2019           75        6.780000
2018           72        6.510000
2017           71        6.420000
2016           70        6.330000
2015           68        6.150000
2014           60        5.420000
2013           55        4.970000
2012           52        4.700000
2011           51        4.610000
2010           48        4.340000
2009           46        4.160000
2008           44        3.980000

========================================

=== Fitur: GrossMargin ===
Total Baris Null : 198
      Jumlah Null  Persentase (%)
Year                             
2024           12        6.060000
2023           12        6.060000
2022           15        7.580000
2021           13        6.570000
2020           14        7.070000
2019           11        5.560000
2018           10        5.050000
2017           12        6.060000
2016           17        8.590000
2015           14        7.070000
2014           10        5.050000
2013           10        5.050000
2012           10        5.050000
2011            8        4.040000
2010           11        5.560000
2009            9        4.550000
2008           10        5.050000

========================================

=== Fitur: OpMargin ===
Total Baris Null : 31
      Jumlah Null  Persentase (%)
Year                             
2024            1        3.230000
2023            1        3.230000
2022            2        6.450000
2021            1        3.230000
2020            2        6.450000
2019            2        6.450000
2018            2        6.450000
2017            2        6.450000
2016            4       12.900000
2015            2        6.450000
2014            1        3.230000
2013            1        3.230000
2012            1        3.230000
2011            2        6.450000
2010            4       12.900000
2009            2        6.450000
2008            1        3.230000

========================================

=== Fitur: NetMargin ===
Total Baris Null : 31
      Jumlah Null  Persentase (%)
Year                             
2024            1        3.230000
2023            1        3.230000
2022            2        6.450000
2021            1        3.230000
2020            2        6.450000
2019            2        6.450000
2018            2        6.450000
2017            2        6.450000
2016            4       12.900000
2015            2        6.450000
2014            1        3.230000
2013            1        3.230000
2012            1        3.230000
2011            2        6.450000
2010            4       12.900000
2009            2        6.450000
2008            1        3.230000

========================================

=== Fitur: CFO_TA ===
Total Baris Null : 2
      Jumlah Null  Persentase (%)
Year                             
2011            1       50.000000
2010            1       50.000000

========================================

=== Fitur: CFO_TL ===
Total Baris Null : 2
      Jumlah Null  Persentase (%)
Year                             
2011            1       50.000000
2010            1       50.000000

========================================

=== Fitur: CFO_Sales ===
Total Baris Null : 33
      Jumlah Null  Persentase (%)
Year                             
2024            1        3.030000
2023            1        3.030000
2022            2        6.060000
2021            1        3.030000
2020            2        6.060000
2019            2        6.060000
2018            2        6.060000
2017            2        6.060000
2016            4       12.120000
2015            2        6.060000
2014            1        3.030000
2013            1        3.030000
2012            1        3.030000
2011            3        9.090000
2010            5       15.150000
2009            2        6.060000
2008            1        3.030000

========================================

=== Fitur: NetCash_TA ===
Total Baris Null : 2
      Jumlah Null  Persentase (%)
Year                             
2011            1       50.000000
2010            1       50.000000

========================================

=== Fitur: EPS_proxy ===
Total Baris Null : 1
      Jumlah Null  Persentase (%)
Year                             
2013            1      100.000000

========================================

=== Fitur: Sales_per_share ===
Total Baris Null : 32
      Jumlah Null  Persentase (%)
Year                             
2024            1        3.120000
2023            1        3.120000
2022            2        6.250000
2021            1        3.120000
2020            2        6.250000
2019            2        6.250000
2018            2        6.250000
2017            2        6.250000
2016            4       12.500000
2015            2        6.250000
2014            1        3.120000
2013            2        6.250000
2012            1        3.120000
2011            2        6.250000
2010            4       12.500000
2009            2        6.250000
2008            1        3.120000

========================================

=== Fitur: CFO_per_share ===
Total Baris Null : 3
      Jumlah Null  Persentase (%)
Year                             
2013            1       33.330000
2011            1       33.330000
2010            1       33.330000

========================================

=== Fitur: log_Sales ===
Total Baris Null : 31
      Jumlah Null  Persentase (%)
Year                             
2024            1        3.230000
2023            1        3.230000
2022            2        6.450000
2021            1        3.230000
2020            2        6.450000
2019            2        6.450000
2018            2        6.450000
2017            2        6.450000
2016            4       12.900000
2015            2        6.450000
2014            1        3.230000
2013            1        3.230000
2012            1        3.230000
2011            2        6.450000
2010            4       12.900000
2009            2        6.450000
2008            1        3.230000

========================================

=== Fitur: log_MktCap ===
Total Baris Null : 25
      Jumlah Null  Persentase (%)
Year                             
2024            1        4.000000
2022            1        4.000000
2021            1        4.000000
2020            1        4.000000
2018            1        4.000000
2017            1        4.000000
2016            1        4.000000
2015            1        4.000000
2014            2        8.000000
2013            3       12.000000
2012            1        4.000000
2011            1        4.000000
2010            3       12.000000
2009            2        8.000000
2008            5       20.000000

========================================

=== Fitur: PB ===
Total Baris Null : 25
      Jumlah Null  Persentase (%)
Year                             
2024            1        4.000000
2022            1        4.000000
2021            1        4.000000
2020            1        4.000000
2018            1        4.000000
2017            1        4.000000
2016            1        4.000000
2015            1        4.000000
2014            2        8.000000
2013            3       12.000000
2012            1        4.000000
2011            1        4.000000
2010            3       12.000000
2009            2        8.000000
2008            5       20.000000

========================================

=== Fitur: Sales_growth ===
Total Baris Null : 902
      Jumlah Null  Persentase (%)
Year                             
2024           81        8.980000
2023           60        6.650000
2022           58        6.430000
2021           53        5.880000
2020           56        6.210000
2019           54        5.990000
2018           37        4.100000
2017           20        2.220000
2016           23        2.550000
2015           25        2.770000
2014           28        3.100000
2013           26        2.880000
2012           25        2.770000
2011           24        2.660000
2010           14        1.550000
2009           16        1.770000
2008          302       33.480000

========================================

=== Fitur: NI_growth ===
Total Baris Null : 853
      Jumlah Null  Persentase (%)
Year                             
2024           79        9.260000
2023           57        6.680000
2022           56        6.570000
2021           50        5.860000
2020           52        6.100000
2019           51        5.980000
2018           33        3.870000
2017           15        1.760000
2016           19        2.230000
2015           23        2.700000
2014           26        3.050000
2013           25        2.930000
2012           23        2.700000
2011           19        2.230000
2010           10        1.170000
2009           13        1.520000
2008          302       35.400000

========================================

=== Fitur: TA_growth ===
Total Baris Null : 853
      Jumlah Null  Persentase (%)
Year                             
2024           79        9.260000
2023           57        6.680000
2022           56        6.570000
2021           50        5.860000
2020           52        6.100000
2019           51        5.980000
2018           33        3.870000
2017           15        1.760000
2016           19        2.230000
2015           23        2.700000
2014           26        3.050000
2013           25        2.930000
2012           23        2.700000
2011           19        2.230000
2010           10        1.170000
2009           13        1.520000
2008          302       35.400000

========================================

=== Fitur: Equity_growth ===
Total Baris Null : 853
      Jumlah Null  Persentase (%)
Year                             
2024           79        9.260000
2023           57        6.680000
2022           56        6.570000
2021           50        5.860000
2020           52        6.100000
2019           51        5.980000
2018           33        3.870000
2017           15        1.760000
2016           19        2.230000
2015           23        2.700000
2014           26        3.050000
2013           25        2.930000
2012           23        2.700000
2011           19        2.230000
2010           10        1.170000
2009           13        1.520000
2008          302       35.400000

========================================

=== Fitur: CFO_growth ===
Total Baris Null : 857
      Jumlah Null  Persentase (%)
Year                             
2024           79        9.220000
2023           57        6.650000
2022           56        6.530000
2021           50        5.830000
2020           52        6.070000
2019           51        5.950000
2018           33        3.850000
2017           15        1.750000
2016           19        2.220000
2015           23        2.680000
2014           26        3.030000
2013           25        2.920000
2012           24        2.800000
2011           21        2.450000
2010           11        1.280000
2009           13        1.520000
2008          302       35.240000

========================================
# Kolom fitur numerik (sesuaikan bila perlu)
non_feature = {
    'ticker', 'Year', 'Entity Name', 'Industry Group',
    'target_distress_ppk', 'tahun_distress_ppk',
    'target_distress_neg_equity', 'tahun_distress_neg_equity',
    'target_distress_consecutive_loss', 'tahun_distress_consecutive_loss'
}
num_feature_cols = [c for c in df_fitur.columns
                    if c not in non_feature
                    and pd.api.types.is_numeric_dtype(df_fitur[c])
                    and not c.startswith('industry_')]

# Hanya fitur dengan null cukup berarti, agar heatmap ringkas
null_share = df_fitur[num_feature_cols].isnull().mean()
features = null_share[null_share > 0.002].sort_values(ascending=False).index.tolist()

# --- Heatmap 1: persentase null tiap fitur di tiap sektor ---
heat_sector = pd.DataFrame({
    c: df_fitur.groupby('Industry Group')[c].apply(lambda s: s.isnull().mean() * 100)
    for c in features
}).T  # baris = fitur, kolom = sektor

plt.figure(figsize=(14, 9))
sns.heatmap(heat_sector, cmap='Reds', linewidths=0.3,
            cbar_kws={'label': 'Persentase Nilai Kosong dalam Sektor (%)'})
plt.title('Persentase Nilai Kosong Tiap Fitur di Tiap Sektor')
plt.xlabel('Industry Group'); plt.ylabel('Fitur')
plt.tight_layout(); plt.show()

# --- Heatmap 2: persentase null tiap fitur per tahun ---
heat_year = pd.DataFrame({
    c: df_fitur.groupby('Year')[c].apply(lambda s: s.isnull().mean() * 100)
    for c in features
}).T

plt.figure(figsize=(12, 9))
sns.heatmap(heat_year, cmap='Blues', linewidths=0.3,
            cbar_kws={'label': 'Persentase Nilai Kosong per Tahun (%)'})
plt.title('Persentase Nilai Kosong Tiap Fitur per Tahun Fiskal')
plt.xlabel('Tahun'); plt.ylabel('Fitur')
plt.tight_layout(); plt.show()

Potensi Fitur Missing Indicator

features_with_null = [
    col for col in df_fitur.columns[df_fitur.isnull().any()]
    if col not in ['ticker', 'Year', 'Entity Name']
    and not col.startswith(('target', 'tahun'))
]
features_with_null
['Parent Percent Owned (%)',
 'Percent Owned - All Institutions (%)',
 'Percent Owned - Insiders (%)',
 'CR',
 'QR',
 'Age_When_IPO',
 'WC_Sales',
 'Inventory_CA',
 'Prepaid_CA',
 'Debt_TA',
 'Debt_Equity',
 'NetDebt_EBITDA',
 'PPE_TA',
 'Intang_TA',
 'Altman_X3_EBIT_TA',
 'Altman_X4_MVE_TL',
 'Altman_X5_SalesTA_AssetTurnover',
 'EBITDA_TA',
 'GrossMargin',
 'OpMargin',
 'NetMargin',
 'CFO_TA',
 'CFO_TL',
 'CFO_Sales',
 'NetCash_TA',
 'EPS_proxy',
 'Sales_per_share',
 'CFO_per_share',
 'log_Sales',
 'log_MktCap',
 'PB',
 'Sales_growth',
 'NI_growth',
 'TA_growth',
 'Equity_growth',
 'CFO_growth']

Jaccard Similarity Global

Dihitung secara global tanpa memisahkan jenis target

if len(features_with_null) == 0:
    print("Tidak ada fitur yang memiliki nilai null untuk dianalisis.")
else:
    # 1. Membuat DataFrame berisi missing indicator
    df_missing = df_fitur[features_with_null].isnull()

    num_features = len(features_with_null)
    jaccard_matrix = np.zeros((num_features, num_features))

    # 2. Menghitung Jaccard Similarity
    for i in range(num_features):
        for j in range(num_features):
            col_i = df_missing.iloc[:, i]
            col_j = df_missing.iloc[:, j]

            intersection = (col_i & col_j).sum()
            union = (col_i | col_j).sum()

            if union == 0:
                jaccard_matrix[i, j] = 1.0
            else:
                jaccard_matrix[i, j] = intersection / union

    df_jaccard = pd.DataFrame(jaccard_matrix, index=features_with_null, columns=features_with_null)

    # 3. Membuat mask untuk memotong segitiga atas
    mask = np.triu(np.ones_like(df_jaccard, dtype=bool), k=1)

    # 4. Memvisualisasikan Heatmap
    plt.figure(figsize=(max(6, num_features * 0.4), max(6, num_features * 0.4)))

    sns.heatmap(df_jaccard,
                mask=mask,
                annot=False,             # Tetap tanpa angka agar compact
                cmap='coolwarm',         # Kembali ke palet biru-merah kontras
                vmin=0, vmax=1,          # Rentang mutlak Jaccard 0 hingga 1
                linewidths=0.5,
                square=True,
                cbar_kws={"shrink": .8, "label": "Skor Jaccard"})

    plt.title('Heatmap Jaccard Similarity antar Missing Indicator',
              fontsize=14, fontweight='bold', pad=20)
    
    plt.xticks(rotation=90, fontsize=9)
    plt.yticks(rotation=0, fontsize=9)

    plt.tight_layout()
    plt.show()

if len(features_with_null) == 0:
    print("Tidak ada fitur yang memiliki nilai null untuk dianalisis.")
else:
    # 1. Membuat DataFrame boolean untuk missing indicator
    df_missing = df_fitur[features_with_null].isnull()

    # 2. Inisialisasi dictionary untuk mengelompokkan kolom
    # Key: tuple pola boolean (True/False) dari atas ke bawah
    # Value: list berisi nama-nama kolom yang memiliki pola tersebut
    kelompok_null = {}

    for col in features_with_null:
        # Mengonversi array boolean menjadi tuple agar dapat menjadi key dictionary
        pola = tuple(df_missing[col].values)

        if pola not in kelompok_null:
            kelompok_null[pola] = []

        kelompok_null[pola].append(col)

    # 3. Menampilkan hasil pengelompokan (hanya untuk kelompok yang berisi > 1 kolom)
    print("=== Kelompok Kolom dengan Missing Indicator Sama Persis (Jaccard = 1.0) ===")

    kelompok_ditemukan = False
    nomor_kelompok = 1

    for pola, daftar_kolom in kelompok_null.items():
        # Hanya mencetak jika ada lebih dari 1 kolom dalam kelompok tersebut
        if len(daftar_kolom) > 1:
            kelompok_ditemukan = True
            print(f"Kelompok {nomor_kelompok} ({len(daftar_kolom)} kolom tergabung):")

            # Mencetak nama-nama kolom yang tergabung dalam satu baris agar rapi
            print(f" -> {', '.join(daftar_kolom)}\n")

            nomor_kelompok += 1

    if not kelompok_ditemukan:
        print("Tidak ditemukan kelompok kolom yang pola kekosongannya sama persis (Jaccard = 1.0).")
=== Kelompok Kolom dengan Missing Indicator Sama Persis (Jaccard = 1.0) ===
Kelompok 1 (5 kolom tergabung):
 -> WC_Sales, Altman_X5_SalesTA_AssetTurnover, OpMargin, NetMargin, log_Sales

Kelompok 2 (2 kolom tergabung):
 -> Debt_TA, Debt_Equity

Kelompok 3 (3 kolom tergabung):
 -> Altman_X4_MVE_TL, log_MktCap, PB

Kelompok 4 (3 kolom tergabung):
 -> CFO_TA, CFO_TL, NetCash_TA

Kelompok 5 (3 kolom tergabung):
 -> NI_growth, TA_growth, Equity_growth
missing_indicators_used_global = features_with_null.copy()

if len(missing_indicators_used_global) == 0:
    print("Tidak ada fitur yang memiliki nilai null untuk diproses.")
else:
    # 2. Membuat DataFrame boolean (True jika null, False jika tidak)
    # Hal ini dilakukan di luar loop agar perhitungan Jaccard jauh lebih cepat
    df_missing = df_fitur[missing_indicators_used_global].isnull()

    # Varians untuk masing-masing missing indicator
    # True/False diubah ke 1/0 agar varians bisa dihitung
    varians_missing = df_missing.astype(int).var()

    # 3. Inisialisasi variabel untuk filter
    kolom_dibuang = set()
    threshold = 0.90

    print(f"=== Proses Eliminasi Missing Indicator ===")
    print(f"Jumlah kolom awal : {len(missing_indicators_used_global)}")
    print(f"Threshold Jaccard : {threshold} (Inklusif)\n")

    # 4. Hitung semua pasangan yang memiliki Jaccard >= threshold
    pasangan_mirip = []

    for i in range(len(missing_indicators_used_global)):
        kolom_i = missing_indicators_used_global[i]
        col_i = df_missing[kolom_i]

        for j in range(i + 1, len(missing_indicators_used_global)):
            kolom_j = missing_indicators_used_global[j]
            col_j = df_missing[kolom_j]

            # Perhitungan Jaccard Similarity
            intersection = (col_i & col_j).sum()
            union = (col_i | col_j).sum()

            if union == 0:
                jaccard_sim = 1.0
            else:
                jaccard_sim = intersection / union

            # Simpan hanya pasangan yang lolos threshold
            if jaccard_sim >= threshold:
                pasangan_mirip.append((kolom_i, kolom_j, jaccard_sim))

    # 5. Eliminasi iteratif berdasarkan:
    #    - pasangan aktif dengan Jaccard tertinggi
    #    - varians lebih rendah
    #    - degree aktif lebih kecil
    #    - nama kolom
    while True:
        # Ambil hanya pasangan yang kedua kolomnya belum dibuang
        pasangan_aktif = [
            (kolom_a, kolom_b, skor_jaccard)
            for kolom_a, kolom_b, skor_jaccard in pasangan_mirip
            if kolom_a not in kolom_dibuang and kolom_b not in kolom_dibuang
        ]

        # Jika sudah tidak ada pasangan aktif, selesai
        if len(pasangan_aktif) == 0:
            break

        # Hitung degree aktif (jumlah pasangan aktif) tiap kolom
        degree_aktif = {}
        for kolom_a, kolom_b, _ in pasangan_aktif:
            degree_aktif[kolom_a] = degree_aktif.get(kolom_a, 0) + 1
            degree_aktif[kolom_b] = degree_aktif.get(kolom_b, 0) + 1

        # Ambil pasangan aktif dengan Jaccard tertinggi
        # Tie-breaker tambahan pada nama kolom agar hasil deterministik
        kolom_utama, kolom_pembanding, jaccard_sim = max(
            pasangan_aktif,
            key=lambda x: (x[2], x[0], x[1])
        )

        var_utama = varians_missing[kolom_utama]
        var_pembanding = varians_missing[kolom_pembanding]

        degree_utama = degree_aktif[kolom_utama]
        degree_pembanding = degree_aktif[kolom_pembanding]

        # Tentukan kolom yang dieliminasi
        # 1. Buang yang variansnya lebih rendah
        if var_utama < var_pembanding:
            kolom_dieliminasi = kolom_utama
            kolom_dipertahankan = kolom_pembanding
        elif var_pembanding < var_utama:
            kolom_dieliminasi = kolom_pembanding
            kolom_dipertahankan = kolom_utama

        # 2. Jika varians sama, buang yang degree aktifnya lebih kecil
        elif degree_utama < degree_pembanding:
            kolom_dieliminasi = kolom_utama
            kolom_dipertahankan = kolom_pembanding
        elif degree_pembanding < degree_utama:
            kolom_dieliminasi = kolom_pembanding
            kolom_dipertahankan = kolom_utama

        # 3. Jika masih sama, pakai nama kolom sebagai tie-breaker
        #    Di sini: buang nama kolom yang lebih besar secara alfabetis
        else:
            if kolom_utama > kolom_pembanding:
                kolom_dieliminasi = kolom_utama
                kolom_dipertahankan = kolom_pembanding
            else:
                kolom_dieliminasi = kolom_pembanding
                kolom_dipertahankan = kolom_utama

        kolom_dibuang.add(kolom_dieliminasi)

        print(
            f"[-] Eliminasi: '{kolom_dieliminasi}' "
            f"(Mirip dengan '{kolom_dipertahankan}' | Skor: {jaccard_sim:.4f})"
        )

    # 6. Memperbarui list missing_indicators_used_global hanya dengan kolom yang dipertahankan
    missing_indicators_used_global = [
        col for col in missing_indicators_used_global
        if col not in kolom_dibuang
    ]

    # 7. Menampilkan ringkasan hasil akhir
    print("\n=== Ringkasan Hasil Akhir ===")
    print(f"Jumlah kolom yang dieliminasi : {len(kolom_dibuang)}")
    print(f"Jumlah kolom yang tersisa     : {len(missing_indicators_used_global)}")
    print("\nDaftar kolom yang akan digunakan untuk membuat missing indicator:")
    for col in missing_indicators_used_global:
        print(f" -> {col}")
=== Proses Eliminasi Missing Indicator ===
Jumlah kolom awal : 36
Threshold Jaccard : 0.9 (Inklusif)

[-] Eliminasi: 'log_MktCap' (Mirip dengan 'PB' | Skor: 1.0000)
[-] Eliminasi: 'log_Sales' (Mirip dengan 'WC_Sales' | Skor: 1.0000)
[-] Eliminasi: 'WC_Sales' (Mirip dengan 'OpMargin' | Skor: 1.0000)
[-] Eliminasi: 'TA_growth' (Mirip dengan 'Equity_growth' | Skor: 1.0000)
[-] Eliminasi: 'OpMargin' (Mirip dengan 'NetMargin' | Skor: 1.0000)
[-] Eliminasi: 'NI_growth' (Mirip dengan 'Equity_growth' | Skor: 1.0000)
[-] Eliminasi: 'Debt_TA' (Mirip dengan 'Debt_Equity' | Skor: 1.0000)
[-] Eliminasi: 'NetCash_TA' (Mirip dengan 'CFO_TL' | Skor: 1.0000)
[-] Eliminasi: 'CFO_TL' (Mirip dengan 'CFO_TA' | Skor: 1.0000)
[-] Eliminasi: 'NetMargin' (Mirip dengan 'Altman_X5_SalesTA_AssetTurnover' | Skor: 1.0000)
[-] Eliminasi: 'PB' (Mirip dengan 'Altman_X4_MVE_TL' | Skor: 1.0000)
[-] Eliminasi: 'QR' (Mirip dengan 'CR' | Skor: 0.9972)
[-] Eliminasi: 'Equity_growth' (Mirip dengan 'CFO_growth' | Skor: 0.9953)
[-] Eliminasi: 'Altman_X3_EBIT_TA' (Mirip dengan 'EBITDA_TA' | Skor: 0.9937)
[-] Eliminasi: 'EBITDA_TA' (Mirip dengan 'NetDebt_EBITDA' | Skor: 0.9822)
[-] Eliminasi: 'Altman_X5_SalesTA_AssetTurnover' (Mirip dengan 'Sales_per_share' | Skor: 0.9688)
[-] Eliminasi: 'CFO_growth' (Mirip dengan 'Sales_growth' | Skor: 0.9415)
[-] Eliminasi: 'Sales_per_share' (Mirip dengan 'CFO_Sales' | Skor: 0.9118)

=== Ringkasan Hasil Akhir ===
Jumlah kolom yang dieliminasi : 18
Jumlah kolom yang tersisa     : 18

Daftar kolom yang akan digunakan untuk membuat missing indicator:
 -> Parent Percent Owned (%)
 -> Percent Owned - All Institutions (%)
 -> Percent Owned - Insiders (%)
 -> CR
 -> Age_When_IPO
 -> Inventory_CA
 -> Prepaid_CA
 -> Debt_Equity
 -> NetDebt_EBITDA
 -> PPE_TA
 -> Intang_TA
 -> Altman_X4_MVE_TL
 -> GrossMargin
 -> CFO_TA
 -> CFO_Sales
 -> EPS_proxy
 -> CFO_per_share
 -> Sales_growth
if len(missing_indicators_used_global) == 0:
    print("Tidak ada fitur dalam daftar missing_indicators_used_global untuk dianalisis.")
else:
    # 1. Membuat DataFrame berisi missing indicator hanya untuk kolom yang tersisa
    df_missing = df_fitur[missing_indicators_used_global].isnull()

    # 2. Menginisialisasi matriks kosong dengan dimensi sesuai jumlah kolom yang difilter
    num_features = len(missing_indicators_used_global)
    jaccard_matrix = np.zeros((num_features, num_features))

    # 3. Menghitung Jaccard Similarity untuk setiap pasangan fitur yang tersisa
    for i in range(num_features):
        for j in range(num_features):
            col_i = df_missing.iloc[:, i]
            col_j = df_missing.iloc[:, j]

            # Intersection: Jumlah baris di mana KEDUA fitur bernilai null
            intersection = (col_i & col_j).sum()

            # Union: Jumlah baris di mana SALAH SATU atau KEDUA fitur bernilai null
            union = (col_i | col_j).sum()

            # Menghitung Jaccard Similarity (mencegah pembagian dengan nol)
            if union == 0:
                jaccard_matrix[i, j] = 1.0
            else:
                jaccard_matrix[i, j] = intersection / union

    # 4. Mengubah matriks numpy menjadi DataFrame agar memiliki nama baris dan kolom yang tepat
    df_jaccard = pd.DataFrame(jaccard_matrix,
                              index=missing_indicators_used_global,
                              columns=missing_indicators_used_global)

    # 5. Memvisualisasikan matriks menggunakan Heatmap dari Seaborn
    # Ukuran gambar disesuaikan secara dinamis dengan jumlah fitur yang tersisa
    plt.figure(figsize=(max(8, num_features * 0.8), max(8, num_features * 0.8)))

    sns.heatmap(df_jaccard,
                annot=True,              # Menampilkan angka kemiripan di dalam kotak
                fmt=".2f",               # Membulatkan angka menjadi 2 desimal
                cmap='coolwarm',         # Skema warna (merah = tinggi, biru = rendah)
                vmin=0, vmax=1,          # Rentang nilai Jaccard (0 hingga 1)
                linewidths=0.5,          # Garis pembatas antar kotak
                square=True,             # Memaksa bentuk sel menjadi persegi
                cbar_kws={"shrink": .8}) # Menyesuaikan ukuran colorbar (legenda warna)

    # Memperbarui judul agar mencerminkan bahwa data telah difilter
    plt.title('Heatmap Jaccard Similarity (Setelah Reduksi Dimensi)',
              fontsize=14, fontweight='bold', pad=20)
    plt.xticks(rotation=90)
    plt.yticks(rotation=0)

    plt.tight_layout()
    plt.show()

if len(missing_indicators_used_global) == 0:
    print("Tidak ada fitur dalam daftar missing_indicators_used_global untuk dianalisis.")
else:
    # 1. Membuat DataFrame berisi missing indicator hanya untuk kolom yang tersisa
    df_missing = df_fitur[missing_indicators_used_global].isnull()

    # 2. Menginisialisasi matriks kosong dengan dimensi sesuai jumlah kolom yang difilter
    num_features = len(missing_indicators_used_global)
    jaccard_matrix = np.zeros((num_features, num_features))

    # 3. Menghitung Jaccard Similarity untuk setiap pasangan fitur yang tersisa
    for i in range(num_features):
        for j in range(num_features):
            col_i = df_missing.iloc[:, i]
            col_j = df_missing.iloc[:, j]

            # Intersection: Jumlah baris di mana KEDUA fitur bernilai null
            intersection = (col_i & col_j).sum()

            # Union: Jumlah baris di mana SALAH SATU atau KEDUA fitur bernilai null
            union = (col_i | col_j).sum()

            # Menghitung Jaccard Similarity (mencegah pembagian dengan nol)
            if union == 0:
                jaccard_matrix[i, j] = 1.0
            else:
                jaccard_matrix[i, j] = intersection / union

    # 4. Mengubah matriks numpy menjadi DataFrame 
    df_jaccard = pd.DataFrame(jaccard_matrix,
                              index=missing_indicators_used_global,
                              columns=missing_indicators_used_global)

    # ==========================================
    # MEMBUAT MASKING UNTUK SEGITIGA ATAS
    # ==========================================
    mask = np.triu(np.ones_like(df_jaccard, dtype=bool), k=1)

    # 5. Memvisualisasikan matriks menggunakan Heatmap dari Seaborn
    # Ukuran gambar dibuat lebih padat (pengali 0.4) karena tidak ada teks di dalam kotak
    plt.figure(figsize=(max(6, num_features * 0.4), max(6, num_features * 0.4)))

    sns.heatmap(df_jaccard,
                mask=mask,               # Menerapkan topeng untuk menyembunyikan sisi atas
                annot=False,             # Mematikan teks angka di dalam kotak
                cmap='coolwarm',         # Tetap menggunakan skema warna biru-merah
                vmin=0, vmax=1,          # Rentang nilai Jaccard (0 hingga 1)
                linewidths=0.5,          # Garis pembatas antar kotak
                square=True,             # Memaksa bentuk sel menjadi persegi
                cbar_kws={"shrink": .8, "label": "Skor Jaccard"}) # Label pada colorbar

    plt.title('Heatmap Jaccard Similarity (Setelah Reduksi Dimensi)',
              fontsize=11, fontweight='bold', pad=20)
    
    # Membesarkan sedikit font pada sumbu agar mudah dibaca
    plt.xticks(rotation=90, fontsize=10)
    plt.yticks(rotation=0, fontsize=10)

    plt.tight_layout()
    plt.show()

Filter Persentase Fitur Null thd Tiap Target

if len(missing_indicators_used_global) == 0:
    print("Daftar missing_indicators_used_global kosong. Tidak ada yang perlu dihitung.")
else:
    total_baris = len(df_fitur)
    hasil_perhitungan = []

    # Menghitung jumlah dan persentase null untuk setiap kolom
    for col in missing_indicators_used_global:
        jumlah_null = df_fitur[col].isnull().sum()
        persentase_null = (jumlah_null / total_baris) * 100

        hasil_perhitungan.append({
            'Nama Fitur': col,
            'Jumlah Null': jumlah_null,
            'Persentase (%)': persentase_null
        })

    # Mengonversi list dictionary menjadi DataFrame
    df_rekap_null = pd.DataFrame(hasil_perhitungan)

    # Mengurutkan data dari persentase terbesar ke terkecil
    df_rekap_null = df_rekap_null.sort_values(by='Persentase (%)', ascending=False).reset_index(drop=True)

    # Membulatkan nilai persentase agar tampilannya lebih rapi (2 angka di belakang koma)
    df_rekap_null['Persentase (%)'] = df_rekap_null['Persentase (%)'].round(2)

    # Mencetak hasil
    print(f"=== Rekapitulasi Missing Values Scr Global ===")
    print(f"Total Baris Data (df_fitur): {total_baris}\n")
    print(df_rekap_null.to_string())
=== Rekapitulasi Missing Values Scr Global ===
Total Baris Data (df_fitur): 8489

                              Nama Fitur  Jumlah Null  Persentase (%)
0                              Intang_TA         6726       79.230000
1   Percent Owned - All Institutions (%)         3947       46.500000
2               Parent Percent Owned (%)         3641       42.890000
3           Percent Owned - Insiders (%)         2551       30.050000
4                             Prepaid_CA         1462       17.220000
5                           Inventory_CA         1206       14.210000
6                         NetDebt_EBITDA         1126       13.260000
7                           Sales_growth          902       10.630000
8                                     CR          706        8.320000
9                            GrossMargin          198        2.330000
10                             CFO_Sales           33        0.390000
11                      Altman_X4_MVE_TL           25        0.290000
12                          Age_When_IPO           21        0.250000
13                           Debt_Equity           21        0.250000
14                                PPE_TA           19        0.220000
15                         CFO_per_share            3        0.040000
16                                CFO_TA            2        0.020000
17                             EPS_proxy            1        0.010000
# df_fitur dan missing_indicators_used_global telah terdefinisi sebelumnya

# Mendapatkan list kolom target yang diawali dengan 'target'
kolom_target = [col for col in df_fitur.columns if col.startswith('target')]

if len(missing_indicators_used_global) == 0:
    print("Daftar missing_indicators_used_global kosong. Tidak ada yang perlu dihitung.")
elif len(kolom_target) == 0:
    print("Tidak ditemukan kolom yang namanya diawali dengan 'target' pada df_fitur.")
else:
    # Melakukan iterasi untuk setiap kolom target
    for target in kolom_target:
        print(f"=== Rekapitulasi Missing Values untuk Target: '{target}' ===")

        # Mengambil subset data di mana kolom target saat ini tidak bernilai null
        df_subset = df_fitur[df_fitur[target].notnull()]
        total_baris_subset = len(df_subset)

        # Dikonversi ke int agar tampilannya tidak berupa desimal (misal 15.0)
        target_positif = int(df_subset[target].sum())

        print(f"Total Baris Data (Target '{target}' tidak null): {total_baris_subset}")
        print(f"Total Baris Bertarget Positif: {target_positif} atau {target_positif/total_baris_subset*100:.2f}% dari total baris tipe target tsb\n")

        if total_baris_subset == 0:
            print("Tidak ada data yang valid (semua baris pada target ini null).\n")
            print("="*60 + "\n")
            continue

        hasil_perhitungan = []

        # Menghitung jumlah dan persentase null untuk setiap kolom pada df_subset
        for col in missing_indicators_used_global:
            # Masking boolean untuk fitur yang null
            is_null_mask = df_subset[col].isnull()

            # Perhitungan 1: Total Null
            jumlah_null = is_null_mask.sum()
            persentase_null = (jumlah_null / total_baris_subset) * 100

            # Perhitungan 2: Null DAN Target = 1
            # Menggunakan operator & untuk mencari irisan kedua kondisi
            jumlah_null_target_1 = (is_null_mask & (df_subset[target] == 1)).sum()
            persentase_null_target_1 = (jumlah_null_target_1 / total_baris_subset) * 100

            hasil_perhitungan.append({
                'Nama Fitur': col,
                'Jumlah Null': jumlah_null,
                'Persentase (%)': persentase_null,
                'Null & Target=1': jumlah_null_target_1,
                '% Null & Target=1': persentase_null_target_1
            })

        # Mengonversi list dictionary menjadi DataFrame
        df_rekap_null = pd.DataFrame(hasil_perhitungan)

        # Mengurutkan data dari persentase terbesar ke terkecil
        df_rekap_null = df_rekap_null.sort_values(by='Persentase (%)', ascending=False).reset_index(drop=True)

        # Membulatkan nilai persentase agar tampilannya lebih rapi (2 angka di belakang koma)
        df_rekap_null['Persentase (%)'] = df_rekap_null['Persentase (%)'].round(2)
        df_rekap_null['% Null & Target=1'] = df_rekap_null['% Null & Target=1'].round(2)

        # Mencetak hasil
        print(df_rekap_null.to_string())
        print("\n" + "="*60 + "\n")
=== Rekapitulasi Missing Values untuk Target: 'target_distress_ppk' ===
Total Baris Data (Target 'target_distress_ppk' tidak null): 2583
Total Baris Bertarget Positif: 510 atau 19.74% dari total baris tipe target tsb

                              Nama Fitur  Jumlah Null  Persentase (%)  Null & Target=1  % Null & Target=1
0                              Intang_TA         1579       61.130000              342          13.240000
1   Percent Owned - All Institutions (%)         1280       49.550000              335          12.970000
2               Parent Percent Owned (%)         1137       44.020000              280          10.840000
3           Percent Owned - Insiders (%)          697       26.980000              171           6.620000
4                             Prepaid_CA          447       17.310000              114           4.410000
5                           Inventory_CA          357       13.820000               73           2.830000
6                         NetDebt_EBITDA          300       11.610000               41           1.590000
7                           Sales_growth          213        8.250000               38           1.470000
8                                     CR          207        8.010000               21           0.810000
9                            GrossMargin           52        2.010000                2           0.080000
10                          Age_When_IPO           11        0.430000                0           0.000000
11                           Debt_Equity            9        0.350000                0           0.000000
12                                PPE_TA            6        0.230000                0           0.000000
13                             CFO_Sales            5        0.190000                0           0.000000
14                      Altman_X4_MVE_TL            2        0.080000                2           0.080000
15                                CFO_TA            0        0.000000                0           0.000000
16                             EPS_proxy            0        0.000000                0           0.000000
17                         CFO_per_share            0        0.000000                0           0.000000

============================================================

=== Rekapitulasi Missing Values untuk Target: 'target_distress_neg_equity' ===
Total Baris Data (Target 'target_distress_neg_equity' tidak null): 6403
Total Baris Bertarget Positif: 116 atau 1.81% dari total baris tipe target tsb

                              Nama Fitur  Jumlah Null  Persentase (%)  Null & Target=1  % Null & Target=1
0                              Intang_TA         5348       83.520000               92           1.440000
1   Percent Owned - All Institutions (%)         2773       43.310000               79           1.230000
2               Parent Percent Owned (%)         2638       41.200000               76           1.190000
3           Percent Owned - Insiders (%)         1947       30.410000               38           0.590000
4                             Prepaid_CA         1109       17.320000               25           0.390000
5                           Inventory_CA          941       14.700000               14           0.220000
6                         NetDebt_EBITDA          923       14.420000                7           0.110000
7                           Sales_growth          720       11.240000               14           0.220000
8                                     CR          567        8.860000                1           0.020000
9                            GrossMargin          152        2.370000                5           0.080000
10                           Debt_Equity           21        0.330000                0           0.000000
11                      Altman_X4_MVE_TL           21        0.330000                4           0.060000
12                             CFO_Sales           15        0.230000                1           0.020000
13                          Age_When_IPO           15        0.230000                0           0.000000
14                                PPE_TA           14        0.220000                0           0.000000
15                         CFO_per_share            3        0.050000                0           0.000000
16                                CFO_TA            2        0.030000                0           0.000000
17                             EPS_proxy            1        0.020000                0           0.000000

============================================================

=== Rekapitulasi Missing Values untuk Target: 'target_distress_consecutive_loss' ===
Total Baris Data (Target 'target_distress_consecutive_loss' tidak null): 5160
Total Baris Bertarget Positif: 565 atau 10.95% dari total baris tipe target tsb

                              Nama Fitur  Jumlah Null  Persentase (%)  Null & Target=1  % Null & Target=1
0                              Intang_TA         4367       84.630000              469           9.090000
1   Percent Owned - All Institutions (%)         2082       40.350000              336           6.510000
2               Parent Percent Owned (%)         1980       38.370000              293           5.680000
3           Percent Owned - Insiders (%)         1540       29.840000              185           3.590000
4                             Prepaid_CA          809       15.680000              110           2.130000
5                         NetDebt_EBITDA          798       15.470000               51           0.990000
6                           Inventory_CA          778       15.080000               61           1.180000
7                           Sales_growth          722       13.990000              128           2.480000
8                                     CR          520       10.080000               25           0.480000
9                            GrossMargin          110        2.130000               13           0.250000
10                      Altman_X4_MVE_TL           17        0.330000                4           0.080000
11                          Age_When_IPO           15        0.290000                0           0.000000
12                           Debt_Equity           14        0.270000                2           0.040000
13                             CFO_Sales           10        0.190000                5           0.100000
14                                PPE_TA            6        0.120000                0           0.000000
15                         CFO_per_share            2        0.040000                0           0.000000
16                                CFO_TA            1        0.020000                0           0.000000
17                             EPS_proxy            1        0.020000                0           0.000000

============================================================
# Mendapatkan list kolom target
kolom_target = [col for col in df_fitur.columns if col.startswith('target')]

# Dictionary untuk menyimpan hasil lolos Tahap 1 saja
fitur_lolos_tahap1 = dict()

if len(missing_indicators_used_global) == 0:
    print("Daftar missing_indicators_used_global kosong.")
elif len(kolom_target) == 0:
    print("Tidak ditemukan kolom target.")
else:
    for target in kolom_target:
        print(f"\n{'='*60}")
        print(f"TAHAP 1: FILTER JUMLAH NULL (MIN. 20) UNTUK '{target}'")
        print(f"{'='*60}")

        # Subset data di mana target valid
        df_subset = df_fitur[df_fitur[target].notnull()]

        if len(df_subset) == 0:
            print("Tidak ada data valid untuk target ini.")
            continue

        lolos = []
        ditolak = []

        # Evaluasi satu per satu fitur global
        for col in missing_indicators_used_global:
            jumlah_null = df_subset[col].isnull().sum()

            if jumlah_null >= 20:
                lolos.append((col, jumlah_null))
            else:
                ditolak.append((col, jumlah_null))

        # Simpan nama fitur yang lolos ke dictionary
        fitur_lolos_tahap1[target] = [item[0] for item in lolos]

        # Menampilkan ringkasan hasil
        print(f"Total fitur dievaluasi: {len(missing_indicators_used_global)}")
        print(f"Lolos (>= 20 null)    : {len(lolos)}")
        print(f"Ditolak (< 20 null)   : {len(ditolak)}\n")

        print("--- DAFTAR FITUR DITOLAK (< 20 Null) ---")
        if len(ditolak) > 0:
            for col, n in ditolak:
                print(f"[-] {col:<35} : {n} null")
        else:
            print("Tidak ada fitur yang ditolak.")

        print("\n--- DAFTAR FITUR LOLOS (>= 20 Null) ---")
        if len(lolos) > 0:
            for col, n in lolos:
                print(f"[+] {col:<35} : {n} null")
        else:
            print("Tidak ada fitur yang lolos.")

============================================================
TAHAP 1: FILTER JUMLAH NULL (MIN. 20) UNTUK 'target_distress_ppk'
============================================================
Total fitur dievaluasi: 18
Lolos (>= 20 null)    : 10
Ditolak (< 20 null)   : 8

--- DAFTAR FITUR DITOLAK (< 20 Null) ---
[-] Age_When_IPO                        : 11 null
[-] Debt_Equity                         : 9 null
[-] PPE_TA                              : 6 null
[-] Altman_X4_MVE_TL                    : 2 null
[-] CFO_TA                              : 0 null
[-] CFO_Sales                           : 5 null
[-] EPS_proxy                           : 0 null
[-] CFO_per_share                       : 0 null

--- DAFTAR FITUR LOLOS (>= 20 Null) ---
[+] Parent Percent Owned (%)            : 1137 null
[+] Percent Owned - All Institutions (%) : 1280 null
[+] Percent Owned - Insiders (%)        : 697 null
[+] CR                                  : 207 null
[+] Inventory_CA                        : 357 null
[+] Prepaid_CA                          : 447 null
[+] NetDebt_EBITDA                      : 300 null
[+] Intang_TA                           : 1579 null
[+] GrossMargin                         : 52 null
[+] Sales_growth                        : 213 null

============================================================
TAHAP 1: FILTER JUMLAH NULL (MIN. 20) UNTUK 'target_distress_neg_equity'
============================================================
Total fitur dievaluasi: 18
Lolos (>= 20 null)    : 12
Ditolak (< 20 null)   : 6

--- DAFTAR FITUR DITOLAK (< 20 Null) ---
[-] Age_When_IPO                        : 15 null
[-] PPE_TA                              : 14 null
[-] CFO_TA                              : 2 null
[-] CFO_Sales                           : 15 null
[-] EPS_proxy                           : 1 null
[-] CFO_per_share                       : 3 null

--- DAFTAR FITUR LOLOS (>= 20 Null) ---
[+] Parent Percent Owned (%)            : 2638 null
[+] Percent Owned - All Institutions (%) : 2773 null
[+] Percent Owned - Insiders (%)        : 1947 null
[+] CR                                  : 567 null
[+] Inventory_CA                        : 941 null
[+] Prepaid_CA                          : 1109 null
[+] Debt_Equity                         : 21 null
[+] NetDebt_EBITDA                      : 923 null
[+] Intang_TA                           : 5348 null
[+] Altman_X4_MVE_TL                    : 21 null
[+] GrossMargin                         : 152 null
[+] Sales_growth                        : 720 null

============================================================
TAHAP 1: FILTER JUMLAH NULL (MIN. 20) UNTUK 'target_distress_consecutive_loss'
============================================================
Total fitur dievaluasi: 18
Lolos (>= 20 null)    : 10
Ditolak (< 20 null)   : 8

--- DAFTAR FITUR DITOLAK (< 20 Null) ---
[-] Age_When_IPO                        : 15 null
[-] Debt_Equity                         : 14 null
[-] PPE_TA                              : 6 null
[-] Altman_X4_MVE_TL                    : 17 null
[-] CFO_TA                              : 1 null
[-] CFO_Sales                           : 10 null
[-] EPS_proxy                           : 1 null
[-] CFO_per_share                       : 2 null

--- DAFTAR FITUR LOLOS (>= 20 Null) ---
[+] Parent Percent Owned (%)            : 1980 null
[+] Percent Owned - All Institutions (%) : 2082 null
[+] Percent Owned - Insiders (%)        : 1540 null
[+] CR                                  : 520 null
[+] Inventory_CA                        : 778 null
[+] Prepaid_CA                          : 809 null
[+] NetDebt_EBITDA                      : 798 null
[+] Intang_TA                           : 4367 null
[+] GrossMargin                         : 110 null
[+] Sales_growth                        : 722 null
fitur_lolos_tahap1
{'target_distress_ppk': ['Parent Percent Owned (%)',
  'Percent Owned - All Institutions (%)',
  'Percent Owned - Insiders (%)',
  'CR',
  'Inventory_CA',
  'Prepaid_CA',
  'NetDebt_EBITDA',
  'Intang_TA',
  'GrossMargin',
  'Sales_growth'],
 'target_distress_neg_equity': ['Parent Percent Owned (%)',
  'Percent Owned - All Institutions (%)',
  'Percent Owned - Insiders (%)',
  'CR',
  'Inventory_CA',
  'Prepaid_CA',
  'Debt_Equity',
  'NetDebt_EBITDA',
  'Intang_TA',
  'Altman_X4_MVE_TL',
  'GrossMargin',
  'Sales_growth'],
 'target_distress_consecutive_loss': ['Parent Percent Owned (%)',
  'Percent Owned - All Institutions (%)',
  'Percent Owned - Insiders (%)',
  'CR',
  'Inventory_CA',
  'Prepaid_CA',
  'NetDebt_EBITDA',
  'Intang_TA',
  'GrossMargin',
  'Sales_growth']}

Fisher’s Exact Test

# Pastikan fitur_lolos_tahap1 sudah terisi dari eksekusi kode sebelumnya
if 'fitur_lolos_tahap1' not in locals() or len(fitur_lolos_tahap1) == 0:
    print("Variabel 'fitur_lolos_tahap1' kosong. Jalankan dulu kode filter Tahap 1 (Batas Null >= 20).")
else:
    # Loop per target
    for target, fitur_list in fitur_lolos_tahap1.items():
        print(f"\n{'='*70}")
        print(f"VISUALISASI FISHER'S EXACT TEST: '{target}'")
        print(f"{'='*70}")

        if len(fitur_list) == 0:
            print(f"Tidak ada fitur yang lolos evaluasi Tahap 1 untuk target {target}. Lewati visualisasi.")
            continue

        # Filter baris yang targetnya valid (tidak null)
        df_subset = df_fitur[df_fitur[target].notnull()].copy()

        results = []

        for col in fitur_list:
            is_null = df_subset[col].isnull()
            target_0 = df_subset[target] == 0
            target_1 = df_subset[target] == 1

            # Hitung matriks kontingensi 2x2
            a = (~is_null & target_0).sum()
            b = (~is_null & target_1).sum()
            c = (is_null & target_0).sum()
            d = (is_null & target_1).sum()

            # Eksekusi Fisher's Exact Test
            odds_ratio, p_value = fisher_exact([[a, b], [c, d]])

            # Hitung -Log10 p-value (tambah 1e-100 agar tidak error log(0) jika p sangat kecil)
            log_p = -np.log10(p_value + 1e-100)

            results.append({
                'Fitur': col,
                'P-Value': p_value,
                'Log_P': log_p,
                'Odds_Ratio': odds_ratio,
                'Signifikan': 'Ya' if p_value < 0.05 else 'Tidak'
            })

        # Konversi ke DataFrame & urutkan dari yang paling signifikan
        df_results = pd.DataFrame(results).sort_values(by='Log_P', ascending=True)

        # ==========================================
        # BAGIAN PLOTTING (VISUALISASI)
        # ==========================================
        # Tinggi figur menyesuaikan jumlah fitur (minimal 4 inci agar tidak terlalu sempit)
        tinggi_fig = max(4, len(fitur_list) * 0.4)
        plt.figure(figsize=(12, tinggi_fig))

        # Warna batang: Hijau untuk signifikan (<0.05), Abu-abu untuk tidak signifikan
        colors = ['#2ecc71' if x == 'Ya' else '#95a5a6' for x in df_results['Signifikan']]

        bars = plt.barh(df_results['Fitur'], df_results['Log_P'], color=colors)

        # Garis batas signifikansi merah ( p = 0.05 => -log10(0.05) ~ 1.301 )
        threshold_val = -np.log10(0.05)
        plt.axvline(x=threshold_val, color='#e74c3c', linestyle='--', linewidth=2,
                    label=f'Threshold Signifikansi (p=0.05)')

        # Pengaturan label dan judul
        plt.title(f"Fisher's Exact Test - Signifikansi Null terhadap {target}\n(Melewati Garis Merah = Signifikan)",
                  fontsize=14, fontweight='bold', pad=15)
        plt.xlabel('-Log10(P-Value)', fontsize=12, fontweight='bold')
        plt.ylabel('Nama Fitur (Hanya yang Null >= 20)', fontsize=12, fontweight='bold')

        # Tambahkan area abu-abu transparan untuk zona "Ditolak" (kiri garis merah)
        plt.axvspan(0, threshold_val, color='gray', alpha=0.1)

        plt.legend(loc='lower right', fontsize=10)

        # Menambahkan teks p-value asli di ujung tiap batang
        for bar, p_val in zip(bars, df_results['P-Value']):
            width = bar.get_width()
            # Format teks: jika p sangat kecil, tampilkan < 0.0001
            teks_label = f" p = {p_val:.4f}" if p_val >= 0.0001 else " p < 0.0001"

            plt.text(width + 0.05, bar.get_y() + bar.get_height()/2, teks_label,
                     va='center', fontsize=10, color='black', fontweight='bold')

        plt.tight_layout()
        plt.show()

        # Cetak Ringkasan Angka
        print(f"Ringkasan {target}:")
        jml_lolos = (df_results['P-Value'] < 0.05).sum()
        jml_gagal = len(df_results) - jml_lolos
        print(f"- Fitur Signifikan (Hijau) : {jml_lolos}")
        print(f"- Fitur Tidak Signifikan (Abu-abu)  : {jml_gagal}\n")

======================================================================
VISUALISASI FISHER'S EXACT TEST: 'target_distress_ppk'
======================================================================

Ringkasan target_distress_ppk:
- Fitur Signifikan (Hijau) : 8
- Fitur Tidak Signifikan (Abu-abu)  : 2


======================================================================
VISUALISASI FISHER'S EXACT TEST: 'target_distress_neg_equity'
======================================================================

Ringkasan target_distress_neg_equity:
- Fitur Signifikan (Hijau) : 5
- Fitur Tidak Signifikan (Abu-abu)  : 7


======================================================================
VISUALISASI FISHER'S EXACT TEST: 'target_distress_consecutive_loss'
======================================================================

Ringkasan target_distress_consecutive_loss:
- Fitur Signifikan (Hijau) : 7
- Fitur Tidak Signifikan (Abu-abu)  : 3
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import fisher_exact

if 'fitur_lolos_tahap1' in locals() and len(fitur_lolos_tahap1) > 0:
    
    n_targets = len(fitur_lolos_tahap1)
    
    # 1. PRE-KALKULASI TINGGI KANVAS
    # Mencari jumlah fitur terbanyak di antara semua target untuk menentukan tinggi kanvas yang proporsional
    max_features = max([len(fitur) for fitur in fitur_lolos_tahap1.values() if len(fitur) > 0], default=0)
    
    if max_features > 0:
        tinggi_fig = max(5, max_features * 0.4)
        
        # 2. INISIALISASI KANVAS TUNGGAL (1 Baris, n_targets Kolom)
        fig, axes = plt.subplots(nrows=1, ncols=n_targets, figsize=(8 * n_targets, tinggi_fig))
        
        # Penyesuaian jika hanya ada 1 target agar iterable
        if n_targets == 1:
            axes = [axes]
            
        for i, (target, fitur_list) in enumerate(fitur_lolos_tahap1.items()):
            ax = axes[i]
            
            if len(fitur_list) == 0:
                ax.set_title(f"Target: {target}\n(Tidak ada fitur lolos)", fontsize=12, fontweight='bold')
                ax.axis('off') # Sembunyikan garis axis jika kosong
                continue

            df_subset = df_fitur[df_fitur[target].notnull()].copy()
            results = []

            # 3. PERHITUNGAN FISHER'S EXACT (Diam tanpa print)
            for col in fitur_list:
                is_null = df_subset[col].isnull()
                target_0 = df_subset[target] == 0
                target_1 = df_subset[target] == 1

                a = (~is_null & target_0).sum()
                b = (~is_null & target_1).sum()
                c = (is_null & target_0).sum()
                d = (is_null & target_1).sum()

                odds_ratio, p_value = fisher_exact([[a, b], [c, d]])
                log_p = -np.log10(p_value + 1e-100)

                results.append({
                    'Fitur': col,
                    'P-Value': p_value,
                    'Log_P': log_p,
                    'Signifikan': 'Ya' if p_value < 0.05 else 'Tidak'
                })

            df_results = pd.DataFrame(results).sort_values(by='Log_P', ascending=True)

            # 4. PLOTTING KE DALAM SUBPLOT (ax)
            colors = ['#2ecc71' if x == 'Ya' else '#95a5a6' for x in df_results['Signifikan']]
            bars = ax.barh(df_results['Fitur'], df_results['Log_P'], color=colors)

            # Garis batas signifikansi (p=0.05)
            threshold_val = -np.log10(0.05)
            ax.axvline(x=threshold_val, color='#e74c3c', linestyle='--', linewidth=2, 
                       label='Threshold (p=0.05)')
            ax.axvspan(0, threshold_val, color='gray', alpha=0.1)

            # Teks dan Labeling
            ax.set_title(f"Signifikansi Null thdp {target}", fontsize=13, fontweight='bold', pad=15)
            ax.set_xlabel('-Log10(P-Value)', fontsize=11, fontweight='bold')
            
            # Agar kanvas rapi, label Y 'Nama Fitur' hanya ditaruh di grafik paling kiri
            if i == 0:
                ax.set_ylabel('Nama Fitur (Null >= 20)', fontsize=11, fontweight='bold')
                
            ax.legend(loc='lower right', fontsize=9)

            # 5. PELEBARAN SUMBU X UNTUK TEKS (Anti-Terpotong)
            # Karena kita akan menaruh teks p-value di ujung batang, kita harus memperlebar batas X-axis
            max_x = df_results['Log_P'].max()
            ax.set_xlim(0, max(max_x * 1.3, threshold_val * 1.5))

            # Injeksi nilai p-value di ujung setiap batang
            for bar, p_val in zip(bars, df_results['P-Value']):
                width = bar.get_width()
                teks_label = f" p = {p_val:.4f}" if p_val >= 0.0001 else " p < 0.0001"
                
                # Menambahkan sedikit jarak padding (max_x * 0.02) agar teks tidak menempel keras di bar
                ax.text(width + (max_x * 0.02), bar.get_y() + bar.get_height()/2, teks_label,
                        va='center', fontsize=9, color='black', fontweight='bold')

        plt.tight_layout()
        plt.show()

# 1. PERSIAPAN DICTIONARY PENYIMPANAN FINAL
fitur_lolos_tahap2_fisher = dict()

# Pastikan fitur_lolos_tahap1 sudah ada dari eksekusi sebelumnya
if 'fitur_lolos_tahap1' not in locals() or len(fitur_lolos_tahap1) == 0:
    print("Variabel 'fitur_lolos_tahap1' tidak ditemukan. Jalankan filter Tahap 1 terlebih dahulu.")
else:
    for target, fitur_list in fitur_lolos_tahap1.items():
        print(f"\n{'='*70}")
        print(f"TAHAP 2: EKSEKUSI FISHER'S EXACT TEST UNTUK '{target}'")
        print(f"{'='*70}")

        if len(fitur_list) == 0:
            print(f"Tidak ada fitur yang dievaluasi untuk {target}.")
            fitur_lolos_tahap2_fisher[target] = []
            continue

        df_subset = df_fitur[df_fitur[target].notnull()]
        lolos_fisher = []

        # Eksekusi uji statistik
        for col in fitur_list:
            is_null = df_subset[col].isnull()
            target_0 = df_subset[target] == 0
            target_1 = df_subset[target] == 1

            a = (~is_null & target_0).sum()
            b = (~is_null & target_1).sum()
            c = (is_null & target_0).sum()
            d = (is_null & target_1).sum()

            odds_ratio, p_value = fisher_exact([[a, b], [c, d]])

            # SIMPAN HANYA JIKA P-VALUE < 0.05
            if p_value < 0.05:
                lolos_fisher.append(col)

        # Simpan list fitur yang lolos ke dalam dictionary final
        fitur_lolos_tahap2_fisher[target] = lolos_fisher

        print(f"Dari {len(fitur_list)} fitur yang masuk, {len(lolos_fisher)} fitur LOLOS (p < 0.05).")
        print(f"Daftar tersimpan di: fitur_lolos_tahap2_fisher['{target}']")

======================================================================
TAHAP 2: EKSEKUSI FISHER'S EXACT TEST UNTUK 'target_distress_ppk'
======================================================================
Dari 10 fitur yang masuk, 8 fitur LOLOS (p < 0.05).
Daftar tersimpan di: fitur_lolos_tahap2_fisher['target_distress_ppk']

======================================================================
TAHAP 2: EKSEKUSI FISHER'S EXACT TEST UNTUK 'target_distress_neg_equity'
======================================================================
Dari 12 fitur yang masuk, 5 fitur LOLOS (p < 0.05).
Daftar tersimpan di: fitur_lolos_tahap2_fisher['target_distress_neg_equity']

======================================================================
TAHAP 2: EKSEKUSI FISHER'S EXACT TEST UNTUK 'target_distress_consecutive_loss'
======================================================================
Dari 10 fitur yang masuk, 7 fitur LOLOS (p < 0.05).
Daftar tersimpan di: fitur_lolos_tahap2_fisher['target_distress_consecutive_loss']

Kolom Terpilih untuk Missing Indicator

kolom_missing_indicator = fitur_lolos_tahap2_fisher.copy()

# cek hasil
for key_target in kolom_missing_indicator:
    print(f"Key: {key_target}")
    print('Kolom yang akan dibuatkan missing indicatornya:')
    print(kolom_missing_indicator[key_target])
    print()
Key: target_distress_ppk
Kolom yang akan dibuatkan missing indicatornya:
['Parent Percent Owned (%)', 'Percent Owned - All Institutions (%)', 'Percent Owned - Insiders (%)', 'CR', 'Prepaid_CA', 'NetDebt_EBITDA', 'Intang_TA', 'GrossMargin']

Key: target_distress_neg_equity
Kolom yang akan dibuatkan missing indicatornya:
['Parent Percent Owned (%)', 'Percent Owned - All Institutions (%)', 'CR', 'NetDebt_EBITDA', 'Altman_X4_MVE_TL']

Key: target_distress_consecutive_loss
Kolom yang akan dibuatkan missing indicatornya:
['Parent Percent Owned (%)', 'Percent Owned - All Institutions (%)', 'CR', 'Inventory_CA', 'Prepaid_CA', 'NetDebt_EBITDA', 'Sales_growth']
# # kolom_missing_indicator harus sudah terdefinisi

# if 'kolom_missing_indicator' not in locals() or len(kolom_missing_indicator) == 0:
#     print("Variabel 'kolom_missing_indicator' tidak ditemukan. Jalankan kode sebelumnya terlebih dahulu.")
# else:

#     # TAHAP FINAL: PEMBUATAN KOLOM BOOLEAN DI DF_FITUR
#     print(f"\n{'='*70}")
#     print("TAHAP FINAL: INJEKSI KOLOM '_is_missing' KE DF_FITUR")
#     print(f"{'='*70}")

#     # Mengumpulkan SEMUA fitur unik yang lolos dari setidaknya satu target
#     # Menggunakan 'set' untuk otomatis menghapus duplikat antar target
#     fitur_global_final = set()
#     for fitur_list in kolom_missing_indicator.values():
#         fitur_global_final.update(fitur_list)

#     fitur_global_final = list(fitur_global_final)

#     if len(fitur_global_final) > 0:
#         print(f"Ditemukan total {len(fitur_global_final)} fitur unik secara global.")
#         print("Memulai pembuatan kolom missing indicator...\n")

#         kolom_dibuat = 0
#         for col in fitur_global_final:
#             nama_kolom_baru = f"{col}_is_missing"

#             # Pengecekan agar tidak membuat kolom ganda jika kode dijalankan 2x
#             if nama_kolom_baru not in df_fitur.columns:
#                 # Membuat kolom biner (1 untuk null, 0 untuk ada isinya)
#                 df_fitur[nama_kolom_baru] = df_fitur[col].isnull().astype(int)
#                 print(f"[+] Dibuat: {nama_kolom_baru}")
#                 kolom_dibuat += 1
#             else:
#                 print(f"[-] Terlewati: {nama_kolom_baru} (Kolom sudah ada)")

#         print(f"\nSelesai! {kolom_dibuat} kolom baru berhasil ditambahkan ke df_fitur secara permanen.")
#     else:
#         print("Tidak ada satu pun fitur yang lolos uji Fisher dari seluruh target. Tidak ada kolom baru dibuat.")
# df_fitur

Cross Tab Terhadap Target

Digunakan untuk menganalisis perbedaan jika suatu fitur null atau tidak null memang menghasilkan perubahan proporsi distress.

pd.crosstab(df_fitur['Parent Percent Owned (%)'].isnull(), df_fitur['target_distress_consecutive_loss'])
target_distress_consecutive_loss 0 1
Parent Percent Owned (%)
False 2908 272
True 1687 293
pd.crosstab(df_fitur['Equity_TA'].isnull(), df_fitur['target_distress_consecutive_loss'])
target_distress_consecutive_loss 0 1
Equity_TA
False 4595 565
# Cek hasil crosstab kalau ada yang tanpa null
ct = pd.crosstab(df_fitur['Equity_TA'].isnull(), df_fitur['target_distress_consecutive_loss'])
ct = ct.reindex([False, True], fill_value=0) # TAMBAHAN AGAR ketika suatu col null semua, maka tetap terbuat 2 index (not null dan null)
ct.index = ['Not Null', 'Null']
ct
target_distress_consecutive_loss 0 1
Not Null 4595 565
Null 0 0

TODO: buat visualisasi dengan mosaic plot

# Tempat menyimpan hasil summary (untuk EDA/Analisis)
null_ct_summaries = dict()

# Pastikan fitur_lolos_tahap2_fisher sudah terdefinisi dari tahap sebelumnya
if 'fitur_lolos_tahap2_fisher' not in locals() or len(fitur_lolos_tahap2_fisher) == 0:
    print("Dictionary fitur_lolos_tahap2_fisher belum terdefinisi. Silakan jalankan tahap eksekusi Fisher's Exact Test sebelumnya.")
else:
    # Loop per target berdasarkan key dan list fitur yang ada di dictionary
    for target, fitur_terpilih in fitur_lolos_tahap2_fisher.items():
        print(f"\n{'='*60}")
        print(f"ANALISIS PROPORSI DISTRESS {target} TERHADAP KETERISIAN DATA")
        print(f"{'='*60}")

        if len(fitur_terpilih) == 0:
            print(f"Tidak ada fitur missing indicator yang lolos uji Fisher untuk {target}. Melewati visualisasi.")
            continue

        # Filter baris yang targetnya tidak null
        df_check = df_fitur[df_fitur[target].notna()].copy()

        summary_list = []

        # Menyesuaikan jumlah fitur dengan list fitur yang lolos per target
        num_features = len(fitur_terpilih)
        rows = (num_features // 4) + (1 if num_features % 4 != 0 else 0)

        if rows == 0:
            continue

        fig, axes = plt.subplots(rows, 4, figsize=(20, 5 * rows))

        # Penanganan dimensi axes
        if num_features == 1:
            axes = [axes]
        else:
            axes = axes.flatten()

        # Iterasi hanya pada fitur yang lolos untuk target saat ini
        for i, col in enumerate(fitur_terpilih):
            # Hitung jumlah Null vs Not Null
            is_null = df_check[col].isnull()
            n_null = int(is_null.sum())
            n_notnull = int((~is_null).sum())
            total = n_null + n_notnull
            prop_null = (n_null / total) if total > 0 else 0

            # Hitung Crosstab proporsi
            ct = pd.crosstab(is_null, df_check[target], normalize='index')

            # Reindex agar matriks tetap 2x2
            ct = ct.reindex(index=[False, True], fill_value=0)
            ct = ct.reindex(columns=[0, 1], fill_value=0)
            ct.index = ['Not Null', 'Null']

            # Delta = P(1|Null) - P(1|Not Null)
            delta = ct.loc['Null', 1] - ct.loc['Not Null', 1]

            summary_list.append({
                'Fitur': col,
                'Total_Null': n_null,
                'Prop_Null (%)': prop_null * 100,
                'Delta_Prob_Target_1 (%)': delta * 100
            })

            # Plotting
            ax = axes[i]
            ct.plot(kind='bar', stacked=True, ax=ax, color=['#3498db', '#e74c3c'], legend=True)
            ax.set_title(f"{col}\nNull: {prop_null:.2%}\nDelta Target 1: {delta:+.1%}",
                         fontsize=10, fontweight='bold')

            ax.set_xticklabels([f'Ada\n(n={n_notnull})', f'Null\n(n={n_null})'], rotation=0)
            ax.set_xlabel('')

            # Label persen pada bagian merah (Target=1)
            if len(ax.containers) > 1:
                for bar in ax.containers[1]:
                    height = bar.get_height()
                    if height > 0.05:
                        ax.text(bar.get_x() + bar.get_width()/2, bar.get_y() + height/2,
                                f'{height:.1%}', ha='center', va='center',
                                color='white', fontweight='bold', fontsize=9)

        # Hapus axes sisa
        for j in range(i + 1, len(axes)):
            fig.delaxes(axes[j])

        plt.tight_layout()
        plt.show()

        # =========================
        # RINGKASAN SELURUH FITUR
        # =========================
        df_summary = pd.DataFrame(summary_list)

        # Urutkan berdasarkan besarnya pengaruh (Absolute Delta)
        df_summary = df_summary.sort_values(by='Delta_Prob_Target_1 (%)', key=abs, ascending=False)

        null_ct_summaries[target] = df_summary

        print(f"\nRingkasan Pengaruh Seluruh Missing Indicator yang Lolos terhadap {target}:")
        print(df_summary.to_string(index=False))
        print("\n" + "-"*60)

============================================================
ANALISIS PROPORSI DISTRESS target_distress_ppk TERHADAP KETERISIAN DATA
============================================================


Ringkasan Pengaruh Seluruh Missing Indicator yang Lolos terhadap target_distress_ppk:
                               Fitur  Total_Null  Prop_Null (%)  Delta_Prob_Target_1 (%)
                         GrossMargin          52       2.013163               -16.224964
Percent Owned - All Institutions (%)        1280      49.554781                12.741330
                                  CR         207       8.013937               -10.435881
            Parent Percent Owned (%)        1137      44.018583                 8.720262
                          Prepaid_CA         447      17.305459                 6.964030
                      NetDebt_EBITDA         300      11.614402                -6.876478
        Percent Owned - Insiders (%)         697      26.984127                 6.559167
                           Intang_TA        1579      61.130468                 4.926210

------------------------------------------------------------

============================================================
ANALISIS PROPORSI DISTRESS target_distress_neg_equity TERHADAP KETERISIAN DATA
============================================================


Ringkasan Pengaruh Seluruh Missing Indicator yang Lolos terhadap target_distress_neg_equity:
                               Fitur  Total_Null  Prop_Null (%)  Delta_Prob_Target_1 (%)
                    Altman_X4_MVE_TL          21       0.327971                17.292683
Percent Owned - All Institutions (%)        2773      43.307824                 1.829616
            Parent Percent Owned (%)        2638      41.199438                 1.818553
                                  CR         567       8.855224                -1.794161
                      NetDebt_EBITDA         923      14.415118                -1.230655

------------------------------------------------------------

============================================================
ANALISIS PROPORSI DISTRESS target_distress_consecutive_loss TERHADAP KETERISIAN DATA
============================================================


Ringkasan Pengaruh Seluruh Missing Indicator yang Lolos terhadap target_distress_consecutive_loss:
                               Fitur  Total_Null  Prop_Null (%)  Delta_Prob_Target_1 (%)
Percent Owned - All Institutions (%)        2082      40.348837                 8.698432
                        Sales_growth         722      13.992248                 7.881754
                                  CR         520      10.077519                -6.830239
            Parent Percent Owned (%)        1980      38.372093                 6.244521
                      NetDebt_EBITDA         798      15.465116                -5.392608
                        Inventory_CA         778      15.077519                -3.660980
                          Prepaid_CA         809      15.678295                 3.139667

------------------------------------------------------------
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import pandas as pd
import numpy as np
import math
from statsmodels.graphics.mosaicplot import mosaic

# Tempat menyimpan ringkasan
null_ct_summaries = dict()
cols_per_row = 4  

if 'fitur_lolos_tahap2_fisher' in locals() and len(fitur_lolos_tahap2_fisher) > 0:
    
    # 1. LOOPING UTAMA BERDASARKAN TARGET
    for target, fitur_terpilih in fitur_lolos_tahap2_fisher.items():
        if len(fitur_terpilih) == 0:
            continue
            
        # Hitung kebutuhan baris hanya untuk target ini
        target_rows = math.ceil(len(fitur_terpilih) / cols_per_row)
        
        # 2. MEMBUAT KANVAS BARU UNTUK TIAP TARGET
        # Tinggi disesuaikan dengan jumlah baris spesifik di target ini
        fig = plt.figure(figsize=(20, 5.5 * target_rows))
        
        # GridSpec tunggal untuk mengatur jarak antar baris di dalam gambar ini
        gs = gridspec.GridSpec(target_rows, cols_per_row, hspace=0.35, wspace=0.3)
        
        df_check = df_fitur[df_fitur[target].notna()].copy()
        summary_list = []

        # 3. PROSES PLOTTING MOSAIK
        for i, col in enumerate(fitur_terpilih):
            r = i // cols_per_row
            c = i % cols_per_row
            
            # Tambahkan kotak grafik pada posisi spesifik
            ax = fig.add_subplot(gs[r, c])

            is_null = df_check[col].isnull()
            status_keterisian = np.where(is_null, 'Null', 'Ada')
            status_target = df_check[target].map({0.0: '0', 1.0: '1'}).fillna('0')
            
            n_null = int(is_null.sum())
            n_notnull = int((~is_null).sum())
            total = n_null + n_notnull
            prop_null = (n_null / total) if total > 0 else 0
            
            ct_stats = pd.crosstab(is_null, df_check[target], normalize='index')
            ct_stats = ct_stats.reindex(index=[False, True], fill_value=0)
            ct_stats = ct_stats.reindex(columns=[0, 1], fill_value=0)
            delta = ct_stats.loc[True, 1] - ct_stats.loc[False, 1]
            
            summary_list.append({
                'Fitur': col,
                'Total_Null': n_null,
                'Prop_Null (%)': prop_null * 100,
                'Delta_Prob_Target_1 (%)': delta * 100
            })

            ct_mosaic = pd.crosstab(status_keterisian, status_target)
            data_dict = {}
            for status in ['Ada', 'Null']:
                for tgt in ['0', '1']:
                    try:
                        data_dict[(status, tgt)] = ct_mosaic.loc[status, tgt]
                    except KeyError:
                        data_dict[(status, tgt)] = 0

            props = lambda key: {
                'facecolor': '#FF8A80' if key[1] == '1' else '#A8E6CF',
                'edgecolor': 'none'
            }

            def labelizer(key):
                count = data_dict.get(key, 0)
                status = key[0]
                total_status = data_dict.get((status, '0'), 0) + data_dict.get((status, '1'), 0)
                if total_status == 0 or count == 0:
                    return ""
                pct = (count / total_status) * 100
                return f"{pct:.1f}%" if pct > 3 else ""

            # Gambar mosaik
            mosaic(data_dict, ax=ax, properties=props, labelizer=labelizer, gap=0.025)

            # --- PENYESUAIAN TEKS & FONT ---
            # Judul Nama Fitur (Font 16)
            ax.set_title(col, fontsize=16, fontweight='bold', pad=10)

            # Angka Persentase di dalam Kotak Mosaik (Font 14)
            for text in ax.texts:
                text.set_fontsize(14) 
                text.set_fontweight('bold')
                text.set_color('#111111') 

            # Label Sumbu Axis (Font 14)
            ax.tick_params(axis='both', labelsize=14)

            # INJEKSI JUDUL SEKSI TARGET (Hanya di plot pojok kiri atas tiap gambar)
            if i == 0:
                ax.text(
                    x=0.0, y=1.30,  
                    s=f"Perubahan Proporsi Target {target} terhadap Missing Indicator", 
                    transform=ax.transAxes, 
                    fontsize=20, 
                    fontweight='bold', 
                    ha='left', 
                    va='bottom',
                    color='#2c3e50'
                )

        # Simpan ringkasan ke dalam dictionary
        df_summary = pd.DataFrame(summary_list)
        if not df_summary.empty:
            df_summary = df_summary.sort_values(by='Delta_Prob_Target_1 (%)', key=abs, ascending=False)
        null_ct_summaries[target] = df_summary

        # 4. KONTROL SPASI & RENDER GAMBAR
        # top=0.88 sedikit lebih rendah dari sebelumnya (0.95) untuk menjamin 
        # teks judul raksasa (Font 20) tidak terpotong oleh batas atas gambar.
        plt.subplots_adjust(left=0.05, right=0.95, top=0.88, bottom=0.05)
        
        # Eksekusi plt.show() DI DALAM loop! Ini yang akan mencetak gambar baru per target
        plt.show()

Crosstab Perubahan Proporsi Distress tidak digunakan untuk filtering karena sudah terevaluasi dengan Fisher’s Exact Test, hanya untuk kebutuhan visualisasi saja.

Phi Coef Visualization

if 'kolom_missing_indicator' not in locals() or len(kolom_missing_indicator) == 0:
    print("Dictionary 'kolom_missing_indicator' kosong atau belum terdefinisi.")
else:
    # Loop untuk setiap target
    for target, fitur_terpilih in kolom_missing_indicator.items():
        print(f"\n{'='*70}")
        print(f"VISUALISASI PHI COEFFICIENT (EFFECT SIZE): '{target}'")
        print(f"{'='*70}")

        if len(fitur_terpilih) == 0:
            print(f"Tidak ada fitur yang lolos untuk target {target}. Lewati visualisasi.")
            continue

        # Filter baris yang targetnya valid (tidak null)
        df_subset = df_fitur[df_fitur[target].notnull()].copy()

        # Ekstrak label target (1/0)
        y_true = df_subset[target].astype(int)

        results = []

        # Hitung Phi untuk masing-masing fitur
        for col in fitur_terpilih:
            # Buat array biner: 1 jika Null, 0 jika ada datanya
            is_missing_array = df_subset[col].isnull().astype(int)

            # Hitung Phi Coefficient (MCC)
            phi_score = matthews_corrcoef(y_true, is_missing_array)

            results.append({
                'Fitur': col,
                'Phi_Coefficient': phi_score,
                'Abs_Phi': abs(phi_score)
            })

        # ==========================================
        # PERUBAHAN DI SINI: Urutkan berdasarkan Phi asli
        # ascending=True agar di bar chart, nilai positif tertinggi ada di atas
        # ==========================================
        df_phi = pd.DataFrame(results).sort_values(by='Phi_Coefficient', ascending=True)

        # ==========================================
        # BAGIAN PLOTTING (VISUALISASI)
        # ==========================================
        tinggi_fig = max(4, len(fitur_terpilih) * 0.4)
        plt.figure(figsize=(10, tinggi_fig))

        colors = ['#ff9999' if val > 0 else '#87ceeb' for val in df_phi['Phi_Coefficient']]

        bars = plt.barh(df_phi['Fitur'], df_phi['Phi_Coefficient'], color=colors)

        # Tambahkan garis vertikal di titik 0
        plt.axvline(x=0, color='black', linestyle='-', linewidth=1.5)

        # Pengaturan label dan judul
        plt.title(f"Arah & Kekuatan Hubungan (Phi Coefficient) terhadap {target}\n(Diurutkan dari Paling Negatif ke Paling Positif)",
                  fontsize=12, fontweight='bold', pad=15)
        plt.xlabel('Phi Coefficient (\u03C6)', fontsize=11, fontweight='bold')
        plt.ylabel('Missing Indicator', fontsize=11, fontweight='bold')

        # Menambahkan nilai teks di ujung setiap batang
        for bar, phi_val in zip(bars, df_phi['Phi_Coefficient']):
            if phi_val > 0:
                pos_x = bar.get_width() + 0.005
                alignment = 'left'
            else:
                pos_x = bar.get_width() - 0.005
                alignment = 'right'

            plt.text(pos_x, bar.get_y() + bar.get_height()/2, f"{phi_val:+.3f}",
                     va='center', ha=alignment, fontsize=10, color='black')

        # Limit sumbu X agar simetris
        max_val = df_phi['Abs_Phi'].max()
        plt.xlim(-(max_val + 0.05), (max_val + 0.05))

        # Legenda Kustom
        legend_elements = [
            Patch(facecolor='#ff9999', label='Positif (+): Jika Null, Risiko Distress Naik'),
            Patch(facecolor='#87ceeb', label='Negatif (-): Jika Null, Risiko Distress Turun (Aman)')
        ]
        plt.legend(handles=legend_elements, loc='best', fontsize=9)

        plt.tight_layout()
        plt.show()

        # Cetak Ringkasan Angka
        print(f"Hasil Phi Coeff untuk {target}:")
        df_top = df_phi.sort_values(by='Phi_Coefficient', ascending=False)
        print(df_top[['Fitur', 'Phi_Coefficient']].to_string(index=False))
        print("\n")

======================================================================
VISUALISASI PHI COEFFICIENT (EFFECT SIZE): 'target_distress_ppk'
======================================================================

Hasil Phi Coeff untuk target_distress_ppk:
                               Fitur  Phi_Coefficient
Percent Owned - All Institutions (%)         0.160032
            Parent Percent Owned (%)         0.108745
        Percent Owned - Insiders (%)         0.073139
                          Prepaid_CA         0.066181
                           Intang_TA         0.060323
                      NetDebt_EBITDA        -0.055347
                         GrossMargin        -0.057246
                                  CR        -0.071179



======================================================================
VISUALISASI PHI COEFFICIENT (EFFECT SIZE): 'target_distress_neg_equity'
======================================================================

Hasil Phi Coeff untuk target_distress_neg_equity:
                               Fitur  Phi_Coefficient
                    Altman_X4_MVE_TL         0.074131
Percent Owned - All Institutions (%)         0.067973
            Parent Percent Owned (%)         0.067111
                      NetDebt_EBITDA        -0.032410
                                  CR        -0.038217



======================================================================
VISUALISASI PHI COEFFICIENT (EFFECT SIZE): 'target_distress_consecutive_loss'
======================================================================

Hasil Phi Coeff untuk target_distress_consecutive_loss:
                               Fitur  Phi_Coefficient
Percent Owned - All Institutions (%)         0.136662
            Parent Percent Owned (%)         0.097247
                        Sales_growth         0.087562
                          Prepaid_CA         0.036558
                        Inventory_CA        -0.041952
                      NetDebt_EBITDA        -0.062442
                                  CR        -0.065846

if 'kolom_missing_indicator' in locals() and len(kolom_missing_indicator) > 0:
    
    # 1. FILTER TARGET VALID & PRE-KALKULASI DIMENSI
    valid_targets = {k: v for k, v in kolom_missing_indicator.items() if len(v) > 0}
    n_targets = len(valid_targets)
    
    if n_targets > 0:
        # Cari jumlah fitur terbanyak untuk menyesuaikan tinggi figur
        max_features = max([len(v) for v in valid_targets.values()], default=0)
        tinggi_fig = max(5, max_features * 0.5)
        
        # 2. INISIALISASI KANVAS YANG LEBIH PADAT (Lebar diubah dari 8 ke 5.5 per target)
        fig, axes = plt.subplots(nrows=1, ncols=n_targets, figsize=(5.5 * n_targets, tinggi_fig))
        
        if n_targets == 1:
            axes = [axes]
            
        # 3. PROSES PERHITUNGAN DAN PLOTTING
        for i, (target_key, fitur_terpilih) in enumerate(valid_targets.items()):
            ax = axes[i]

            # Proteksi KeyError nama kolom target
            nama_kolom_target = target_key
            if target_key not in df_fitur.columns:
                nama_kolom_target = f"target_distress_{target_key}"
                if nama_kolom_target not in df_fitur.columns:
                    continue

            df_subset = df_fitur[df_fitur[nama_kolom_target].notnull()].copy()
            y_true = df_subset[nama_kolom_target].astype(int)

            results = []

            for col in fitur_terpilih:
                is_missing_array = df_subset[col].isnull().astype(int)
                phi_score = matthews_corrcoef(y_true, is_missing_array)

                results.append({
                    'Fitur': col,
                    'Phi_Coefficient': phi_score,
                    'Abs_Phi': abs(phi_score)
                })

            df_phi = pd.DataFrame(results).sort_values(by='Phi_Coefficient', ascending=True)

            colors = ['#ff9999' if val > 0 else '#87ceeb' for val in df_phi['Phi_Coefficient']]
            bars = ax.barh(df_phi['Fitur'], df_phi['Phi_Coefficient'], color=colors)

            ax.axvline(x=0, color='black', linestyle='-', linewidth=1.5)

            # Judul dan Label
            ax.set_title(f"Target:\n{target_key}", fontsize=14, fontweight='bold', pad=15)
            ax.set_xlabel('Phi Coefficient (\u03C6)', fontsize=12, fontweight='bold')
            
            if i == 0:
                ax.set_ylabel('Missing Indicator', fontsize=12, fontweight='bold')

            # Berikan ruang batas X sedikit lebih longgar (1.4) karena lebar plot sekarang lebih kecil
            max_val = df_phi['Abs_Phi'].max()
            ax.set_xlim(-(max_val * 1.4), (max_val * 1.4))

            # Injeksi nilai teks di ujung setiap batang
            for bar, phi_val in zip(bars, df_phi['Phi_Coefficient']):
                padding = max_val * 0.04 
                
                if phi_val > 0:
                    pos_x = bar.get_width() + padding
                    alignment = 'left'
                else:
                    pos_x = bar.get_width() - padding
                    alignment = 'right'

                ax.text(pos_x, bar.get_y() + bar.get_height()/2, f"{phi_val:+.3f}",
                        va='center', ha=alignment, fontsize=11, color='black', fontweight='bold')

        # 4. LEGENDA GLOBAL & JUDUL UTAMA
        legend_elements = [
            Patch(facecolor='#ff9999', label='Positif (+): Jika Null, Risiko Distress Naik'),
            Patch(facecolor='#87ceeb', label='Negatif (-): Jika Null, Risiko Distress Turun (Aman)')
        ]
        
        fig.legend(handles=legend_elements, loc='lower center', bbox_to_anchor=(0.5, 0.02), ncol=2, fontsize=12)
        
        fig.suptitle('Arah & Kekuatan Hubungan (Phi Coefficient) terhadap Seluruh Target', 
                     fontsize=18, fontweight='bold', y=0.98)

        # 5. MERAPIKAN TATA LETAK (w_pad dirapatkan dari 3.0 ke 1.5)
        plt.tight_layout(rect=[0, 0.08, 1, 0.92], w_pad=1.5)
        plt.show()

kolom_missing_indicator
{'target_distress_ppk': ['Parent Percent Owned (%)',
  'Percent Owned - All Institutions (%)',
  'Percent Owned - Insiders (%)',
  'CR',
  'Prepaid_CA',
  'NetDebt_EBITDA',
  'Intang_TA',
  'GrossMargin'],
 'target_distress_neg_equity': ['Parent Percent Owned (%)',
  'Percent Owned - All Institutions (%)',
  'CR',
  'NetDebt_EBITDA',
  'Altman_X4_MVE_TL'],
 'target_distress_consecutive_loss': ['Parent Percent Owned (%)',
  'Percent Owned - All Institutions (%)',
  'CR',
  'Inventory_CA',
  'Prepaid_CA',
  'NetDebt_EBITDA',
  'Sales_growth']}

Eksplorasi Data Lainnya

# 1. Menghitung jumlah nilai unik dari Parent Percent Owned (%) untuk setiap ticker
cek_konsistensi = df_fitur.groupby('ticker')['Parent Percent Owned (%)'].nunique()

# 2. Mendapatkan daftar ticker yang memiliki lebih dari 1 nilai unik
ticker_berubah = cek_konsistensi[cek_konsistensi > 1].index.tolist()

# 3. Evaluasi dan Tampilkan Hasil
if len(ticker_berubah) > 0:
    print(f"Ditemukan {len(ticker_berubah)} perusahaan yang persentase kepemilikan induknya berubah.\n")
    
    # Memfilter dataframe asli untuk melihat detail perubahan dari tahun ke tahun
    df_detail_perubahan = df_fitur[df_fitur['ticker'].isin(ticker_berubah)][
        ['ticker', 'Year', 'Parent Percent Owned (%)']
    ].sort_values(by=['ticker', 'Year'])
    
    # Menampilkan hasilnya
    display(df_detail_perubahan)
else:
    print("Persentase kepemilikan induk semua perusahaan konsisten (tidak berubah) sepanjang tahun.")
Persentase kepemilikan induk semua perusahaan konsisten (tidak berubah) sepanjang tahun.

Sebaran Tahun Fiskal

targets = [col for col in df_fitur.columns if col.startswith('target')]

for target in targets:
    print('\nSebaran Baris Data pada Tiap Tahun Fiskal untuk Target', target, ':')
    print(df_fitur[df_fitur[target].notna()]['Year'].value_counts().sort_index())

Sebaran Baris Data pada Tiap Tahun Fiskal untuk Target target_distress_ppk :
Year
2020    611
2021    654
2022    667
2023    651
Name: count, dtype: Int64

Sebaran Baris Data pada Tiap Tahun Fiskal untuk Target target_distress_neg_equity :
Year
2008    291
2009    302
2010    310
2011    328
2012    347
2013    370
2014    395
2015    417
2016    432
2017    440
2018    469
2019    514
2020    557
2021    596
2022    635
Name: count, dtype: Int64

Sebaran Baris Data pada Tiap Tahun Fiskal untuk Target target_distress_consecutive_loss :
Year
2008    302
2009    287
2010    289
2011    303
2012    315
2013    331
2014    344
2015    346
2016    340
2017    336
2018    354
2019    386
2020    401
2021    399
2022    427
Name: count, dtype: Int64
# --- 1. Persiapan Target ---
targets = [col for col in df_fitur.columns if col.startswith('target')]

# --- 2. Setup Canvas (1 Baris, 3 Kolom) ---
# figsize=(18, 6) memberikan rasio yang pas untuk 3 chart berdampingan
fig, axes = plt.subplots(1, len(targets), figsize=(18, 6))

# Handle jika target hanya 1 (bungkus jadi list agar loop aman)
if len(targets) == 1:
    axes = [axes]

# --- 3. Loop Visualisasi ---
for i, target in enumerate(targets):
    ax = axes[i]

    # A. Olah Data
    # Filter notna -> ambil kolom Year -> hitung -> urutkan tahun
    data_counts = df_fitur[df_fitur[target].notna()]['Year'].value_counts().sort_index()

    # Ubah index tahun menjadi string agar jarak antar bar rapi
    x = data_counts.index.astype(str)
    y = data_counts.values

    # B. Plot Bar (Volume)
    bars = ax.bar(x, y, color='#3498db', alpha=0.6, label='Jumlah Data', zorder=2)

    # C. Plot Line (Tren)
    ax.plot(x, y, color='#e74c3c', marker='o', linewidth=2, markersize=6, label='Tren', zorder=3)

    # D. Anotasi Angka (Label)
    # Menambahkan angka di atas bar/titik
    for j, val in enumerate(y):
        ax.text(j, val + (val * 0.02), # Posisi sedikit di atas nilai asli
                f'{val}',
                ha='center', va='bottom', fontsize=10, fontweight='normal') # Font tidak bold

    # E. Kosmetik & Layout
    target_name = ""
    if target == 'target_distress_ppk':
        target_name = 'Papan Pemantauan Khusus'
    elif target == 'target_distress_neg_equity':
        target_name = 'Ekuitas Negatif'
    elif target == 'target_distress_consecutive_loss':
        target_name = 'Rugi Bersih 2 Tahun Berturut-turut'
    else:
        target_name = target
    ax.set_title(f'Sebaran Data Point pada Tahun Fiskal untuk Target\nDistress {target_name}', fontsize=12, fontweight='bold')
    ax.set_xlabel('Tahun Fiskal')
    ax.set_ylabel('Jumlah Baris Data')

    # Grid di belakang
    ax.grid(axis='y', linestyle='--', alpha=0.5, zorder=0)

    # [PERBAIKAN 1] Rotasi Label Sumbu X Vertikal (90 derajat)
    ax.tick_params(axis='x', rotation=90)

    # [PERBAIKAN 2] Menambah Headroom Sumbu Y
    # Kita ambil nilai maksimum y, lalu tambah 15% agar angka tidak mentok atas
    y_max = max(y)
    ax.set_ylim(0, y_max * 1.15)

# Agar layout rapi dan judul tidak saling tabrak
plt.tight_layout()
plt.show()

# --- 1. Persiapan Target ---
targets = [col for col in df_fitur.columns if col.startswith('target')]

# --- 2. Setup Canvas ---
fig, axes = plt.subplots(1, len(targets), figsize=(18, 7))

if len(targets) == 1:
    axes = [axes]

# --- 3. Loop Visualisasi ---
for i, target in enumerate(targets):
    ax = axes[i]

    # A. Mapping Nama Target
    target_name = ""
    if target == 'target_distress_ppk':
        target_name = 'Papan Pemantauan Khusus'
    elif target == 'target_distress_neg_equity':
        target_name = 'Ekuitas Negatif'
    elif target == 'target_distress_consecutive_loss':
        target_name = 'Rugi Bersih 2 Tahun Berturut-turut'
    else:
        target_name = target

    # B. Olah Data (Kumulatif)
    counts = df_fitur[df_fitur[target].notna()]['Year'].value_counts().sort_index()
    cumsum_values = counts.cumsum()
    total_data = counts.sum()
    cumsum_pct = (cumsum_values / total_data) * 100

    x = counts.index.astype(str)
    y = cumsum_values.values
    pcts = cumsum_pct.values

    # C. Logika Pewarnaan (Train vs Test)
    # Jika persentase kumulatif <= 80%, anggap sebagai Train (Kuning)
    # Sisanya adalah Test (Biru)
    colors = []
    for pct in pcts:
        if pct <= 80.0:
            colors.append('#f1c40f') # Kuning Emas (Train)
        else:
            colors.append('#3498db') # Biru (Test)

    # D. Plot Bar dengan List Warna
    bars = ax.bar(x, y, color=colors, alpha=0.85, edgecolor='black', linewidth=0.5)

    # E. Anotasi Angka
    for j, (val, pct) in enumerate(zip(y, pcts)):
        label_text = f"{val}\n({pct:.1f}%)"
        ax.text(j, val + (total_data * 0.02),
                label_text,
                ha='center', va='bottom', fontsize=9, fontweight='normal', color='black')

    # F. Garis Ambang Batas
    limit_70 = total_data * 0.70
    limit_80 = total_data * 0.80

    ax.axhline(limit_70, color='#e67e22', linestyle='--', linewidth=1.5, alpha=0.7) # Garis 70%
    ax.axhline(limit_80, color='#c0392b', linestyle='--', linewidth=2)    # Garis 80% (Batas Split)

    # G. Legenda Manual (Kuning vs Biru)
    legend_elements = [
        Patch(facecolor='#f1c40f', edgecolor='black', label='Dataset Train'),
        Patch(facecolor='#3498db', edgecolor='black', label='Dataset Test'),
        plt.Line2D([0], [0], color='#e67e22', linestyle='--', label='Threshold 70%'),
        plt.Line2D([0], [0], color='#c0392b', linestyle='--', label='Threshold 80%')
    ]
    ax.legend(handles=legend_elements, loc='upper left', fontsize='small')

    # H. Kosmetik
    ax.set_title(f'Simulasi Time-Based Split (Train vs Test)\nTarget: {target_name}', fontsize=12, fontweight='bold')
    ax.set_xlabel('Tahun Fiskal')
    ax.set_ylabel('Total Data Point Terakumulasi')
    ax.grid(axis='y', linestyle=':', alpha=0.4)
    ax.tick_params(axis='x', rotation=90)
    ax.set_ylim(0, total_data * 1.35) # Headroom

plt.tight_layout()
plt.show()

Sebaran Label Target (Terprediksi Akan Distress)

targets = [col for col in df_fitur.columns if col.startswith('target')]

for target in targets:
    distress_count = (df_fitur[target]==1).sum()
    all_count_per_indicator = df_fitur[target].notna().sum()
    prop = round(distress_count/all_count_per_indicator*100, 2)
    print('\nSebaran Target Positif pada Tiap Tahun Fiskal', target, ':')
    print(df_fitur[(df_fitur[target].notna()) & (df_fitur[target]==1)]['Year'].value_counts().sort_index())
    print('Total ada',distress_count,'baris data yang dilabeli distress dari',all_count_per_indicator,'total data (',prop,'% )')

Sebaran Target Positif pada Tiap Tahun Fiskal target_distress_ppk :
Year
2020     44
2021     92
2022    168
2023    206
Name: count, dtype: Int64
Total ada 510 baris data yang dilabeli distress dari 2583 total data ( 19.74 % )

Sebaran Target Positif pada Tiap Tahun Fiskal target_distress_neg_equity :
Year
2008     4
2009     3
2010     4
2011     5
2012     3
2013     2
2014     5
2015    11
2016    10
2017     9
2018    13
2019    13
2020    14
2021     9
2022    11
Name: count, dtype: Int64
Total ada 116 baris data yang dilabeli distress dari 6403 total data ( 1.81 % )

Sebaran Target Positif pada Tiap Tahun Fiskal target_distress_consecutive_loss :
Year
2008    36
2009    13
2010    16
2011    19
2012    21
2013    33
2014    43
2015    42
2016    32
2017    29
2018    49
2019    74
2020    63
2021    42
2022    53
Name: count, dtype: Int64
Total ada 565 baris data yang dilabeli distress dari 5160 total data ( 10.95 % )
# --- 1. Persiapan Target ---
targets = [col for col in df_fitur.columns if col.startswith('target')]

# --- 2. Setup Canvas (1 Baris, 3 Kolom) ---
# figsize=(18, 6) memberikan rasio yang pas untuk 3 chart berdampingan
fig, axes = plt.subplots(1, len(targets), figsize=(18, 6))

# Handle jika target hanya 1 (bungkus jadi list agar loop aman)
if len(targets) == 1:
    axes = [axes]

# --- 3. Loop Visualisasi ---
for i, target in enumerate(targets):
    ax = axes[i]

    # A. Olah Data
    # Filter notna -> ambil kolom Year -> hitung -> urutkan tahun
    data_counts = df_fitur[(df_fitur[target].notna()) & (df_fitur[target]==1)]['Year'].value_counts().sort_index()

    # Ubah index tahun menjadi string agar jarak antar bar rapi
    x = data_counts.index.astype(str)
    y = data_counts.values

    # B. Plot Bar (Volume)
    bars = ax.bar(x, y, color='#3498db', alpha=0.6, label='Jumlah Label Distress', zorder=2)

    # C. Plot Line (Tren)
    ax.plot(x, y, color='#e74c3c', marker='o', linewidth=2, markersize=6, label='Tren', zorder=3)

    # D. Anotasi Angka (Label)
    # Menambahkan angka di atas bar/titik
    for j, val in enumerate(y):
        ax.text(j, val + (val * 0.02), # Posisi sedikit di atas nilai asli
                f'{val}',
                ha='center', va='bottom', fontsize=10, fontweight='normal') # Font tidak bold

    # E. Kosmetik & Layout
    target_name = ""
    if target == 'target_distress_ppk':
        target_name = 'Papan Pemantauan Khusus'
    elif target == 'target_distress_neg_equity':
        target_name = 'Ekuitas Negatif'
    elif target == 'target_distress_consecutive_loss':
        target_name = 'Rugi Bersih 2 Tahun Berturut-turut'
    else:
        target_name = target
    ax.set_title(f'Sebaran Label Distress (Target = 1)\nTarget Distress : {target_name}', fontsize=12, fontweight='bold')
    ax.set_xlabel('Tahun Fiskal')
    ax.set_ylabel('Jumlah Baris dengan Label Distress')

    # Grid di belakang
    ax.grid(axis='y', linestyle='--', alpha=0.5, zorder=0)

    # [PERBAIKAN 1] Rotasi Label Sumbu X Vertikal (90 derajat)
    ax.tick_params(axis='x', rotation=90)

    # [PERBAIKAN 2] Menambah Headroom Sumbu Y
    # Kita ambil nilai maksimum y, lalu tambah 15% agar angka tidak mentok atas
    y_max = max(y)
    ax.set_ylim(0, y_max * 1.15)

# Agar layout rapi dan judul tidak saling tabrak
plt.tight_layout()
plt.show()

# --- 1. Persiapan Target ---
targets = [col for col in df_fitur.columns if col.startswith('target')]

# --- 2. Setup Canvas ---
fig, axes = plt.subplots(1, len(targets), figsize=(18, 7))

# Handle jika target hanya 1
if len(targets) == 1:
    axes = [axes]

# --- 3. Loop Visualisasi ---
for i, target in enumerate(targets):
    ax = axes[i]

    # A. Mapping Nama Target
    target_name = ""
    if target == 'target_distress_ppk':
        target_name = 'Papan Pemantauan Khusus'
    elif target == 'target_distress_neg_equity':
        target_name = 'Ekuitas Negatif'
    elif target == 'target_distress_consecutive_loss':
        target_name = 'Rugi Bersih 2 Tahun Berturut-turut'
    else:
        target_name = target

    # B. Olah Data (FILTER HANYA TARGET == 1)
    # Filter notna DAN target==1 -> ambil kolom Year -> hitung -> urutkan tahun
    counts = df_fitur[(df_fitur[target].notna()) & (df_fitur[target] == 1)]['Year'].value_counts().sort_index()

    # Hitung Kumulatif
    cumsum_values = counts.cumsum()
    total_distress = counts.sum() # Total kasus distress
    cumsum_pct = (cumsum_values / total_distress) * 100

    x = counts.index.astype(str)
    y = cumsum_values.values
    pcts = cumsum_pct.values

    # C. Logika Pewarnaan (Train vs Test berdasarkan akumulasi Distress)
    colors = []
    for pct in pcts:
        if pct <= 80.0:
            colors.append('#f1c40f') # Kuning Emas (Train)
        else:
            colors.append('#3498db') # Biru (Test)

    # D. Plot Bar
    bars = ax.bar(x, y, color=colors, alpha=0.85, edgecolor='black', linewidth=0.5)

    # E. Anotasi Angka
    for j, (val, pct) in enumerate(zip(y, pcts)):
        label_text = f"{val}\n({pct:.1f}%)"
        ax.text(j, val + (total_distress * 0.02),
                label_text,
                ha='center', va='bottom', fontsize=9, fontweight='normal', color='black')

    # F. Garis Ambang Batas (Berdasarkan Total Distress)
    limit_70 = total_distress * 0.70
    limit_80 = total_distress * 0.80

    ax.axhline(limit_70, color='#e67e22', linestyle='--', linewidth=1.5, alpha=0.7)
    ax.axhline(limit_80, color='#c0392b', linestyle='--', linewidth=2)

    # G. Legenda Manual
    legend_elements = [
        Patch(facecolor='#f1c40f', edgecolor='black', label='Train'),
        Patch(facecolor='#3498db', edgecolor='black', label='Test'),
        plt.Line2D([0], [0], color='#e67e22', linestyle='--', label='Threshold 70%'),
        plt.Line2D([0], [0], color='#c0392b', linestyle='--', label='Threshold 80%')
    ]
    ax.legend(handles=legend_elements, loc='upper left', fontsize='small')

    # H. Kosmetik
    ax.set_title(f'Akumulasi Label Distress (Target=1)\nTarget: {target_name}', fontsize=12, fontweight='bold')
    ax.set_xlabel('Tahun Fiskal')
    ax.set_ylabel('Total Kumulatif Label Distress')
    ax.grid(axis='y', linestyle=':', alpha=0.4)
    ax.tick_params(axis='x', rotation=90)

    # Headroom
    ax.set_ylim(0, total_distress * 1.35)

plt.tight_layout()
plt.show()

Semuanya selaras dengan proporsi split train test berdasarkan jumlah kumulatif baris data per tahun fiskal pada bagian sebelumnya. Untuk itu, split dataset berlabel distress papan pemantauan khusus dilakukan pada tahun 2022, sedangkan untuk label distress ekuitas negatif dan rugi 2 tahun berturut-turut pada tahun 2019.

Untuk label target papan pemantauan khusus, perbedaan proporsi sebaran label antara train dan test cukup signifikan dari threshold : - Ada 74.6% baris data yang masuk train untuk label papan pemantauan khusus. - Namun, hanya 58.7% saja baris berlabel distress yang masuk ke train.

Tapi tidak masalah karena split di tahun tersebut sudah yang paling mendekati.

DatPrep : Lanjutan FeatEng

Hapus Kolom Industri

Kolom Industri dipertahankan karena untuk keperluan imputasi di dalam tiap fold pada saat cross validation.

# df_fitur = df_fitur.drop(columns=['Industry Group'])
# df_fitur.head(3)

Ubah Penamaan Key Dict Missing Indicator

# Membuat key baru sambil ambil & hapus isi dari key lama
kolom_missing_indicator['ppk'] = kolom_missing_indicator.pop('target_distress_ppk')
kolom_missing_indicator['negeq'] = kolom_missing_indicator.pop('target_distress_neg_equity')
kolom_missing_indicator['conloss'] = kolom_missing_indicator.pop('target_distress_consecutive_loss')

# cek hasil
for key_target in kolom_missing_indicator:
    print(f"Key: {key_target}")
    print('Kolom yang akan dibuatkan missing indicatornya:')
    print(kolom_missing_indicator[key_target])
    print()
Key: ppk
Kolom yang akan dibuatkan missing indicatornya:
['Parent Percent Owned (%)', 'Percent Owned - All Institutions (%)', 'Percent Owned - Insiders (%)', 'CR', 'Prepaid_CA', 'NetDebt_EBITDA', 'Intang_TA', 'GrossMargin']

Key: negeq
Kolom yang akan dibuatkan missing indicatornya:
['Parent Percent Owned (%)', 'Percent Owned - All Institutions (%)', 'CR', 'NetDebt_EBITDA', 'Altman_X4_MVE_TL']

Key: conloss
Kolom yang akan dibuatkan missing indicatornya:
['Parent Percent Owned (%)', 'Percent Owned - All Institutions (%)', 'CR', 'Inventory_CA', 'Prepaid_CA', 'NetDebt_EBITDA', 'Sales_growth']

Split Jenis Target Financial Distress

cols_distress = [col for col in df_fitur.columns if col.startswith('target') or col.startswith('tahun')]
cols_distress
['target_distress_ppk',
 'tahun_distress_ppk',
 'target_distress_neg_equity',
 'tahun_distress_neg_equity',
 'target_distress_consecutive_loss',
 'tahun_distress_consecutive_loss']
[col for col in cols_distress if not col.endswith('ppk')] # tes aja
['target_distress_neg_equity',
 'tahun_distress_neg_equity',
 'target_distress_consecutive_loss',
 'tahun_distress_consecutive_loss']

Pisahkan dataset untuk masing-masing jenis indikator distress dengan melakukan :
- Pilih kolom label target yang bersesuaian, pilih yang tidak null - Drop kolom-kolom dari indikator lain - Rename kolom target dan tahun distress agar selaras semua

# Papan Pemantauan Khusus
df_fitur_ppk = (df_fitur[df_fitur['target_distress_ppk'].notna()]
                .drop([col for col in cols_distress if not col.endswith('ppk')], axis=1)
                .rename(columns=lambda x: x.removesuffix('_ppk'))
                ).reset_index(drop=True)

# Ekuitas Negatif
df_fitur_negeq = (df_fitur[df_fitur['target_distress_neg_equity'].notna()]
                  .drop([col for col in cols_distress if not col.endswith('neg_equity')], axis=1)
                  .rename(columns=lambda x: x.removesuffix('_neg_equity'))
                  ).reset_index(drop=True)

# Rugi 2 Tahun Berturut-turut
df_fitur_conloss = (df_fitur[df_fitur['target_distress_consecutive_loss'].notna()]
                    .drop([col for col in cols_distress if not col.endswith('consecutive_loss')], axis=1)
                    .rename(columns=lambda x: x.removesuffix('_consecutive_loss'))
                    ).reset_index(drop=True)
print('=== Dataset Fitur dengan Label Distress Papan Pemantauan Khusus yang berukuran',df_fitur_ppk.shape,'===')
df_fitur_ppk.head(3)
=== Dataset Fitur dengan Label Distress Papan Pemantauan Khusus yang berukuran (2583, 75) ===
ticker Year Entity Name target_distress tahun_distress Industry Group Parent Percent Owned (%) Percent Owned - All Institutions (%) Percent Owned - Insiders (%) CR QR Age_When_IPO Years_Since_IPO WC_Sales CA_TA CL_TA CashST_TA CashST_CL Inventory_CA Prepaid_CA TL_TA Debt_TA Debt_Equity Equity_TA NetDebt_EBITDA PPE_TA Intang_TA Altman_X1_WC_TA Altman_X3_EBIT_TA Altman_X4_MVE_TL Altman_X5_SalesTA_AssetTurnover ROA ROE EBITDA_TA GrossMargin OpMargin NetMargin CFO_TA CFO_TL CFO_Sales NetCash_TA EPS_proxy Sales_per_share CFO_per_share log_TA log_Sales log_MktCap PB Sales_growth NI_growth TA_growth Equity_growth CFO_growth industry_Banks industry_Capital Goods industry_Commercial and Professional Services industry_Consumer Discretionary Distribution and Retail industry_Consumer Durables and Apparel industry_Consumer Services industry_Consumer Staples Distribution and Retail industry_Energy industry_Financial Services industry_Food Beverage and Tobacco industry_Health Care Equipment and Services industry_Household and Personal Products industry_Insurance industry_Materials industry_Media and Entertainment industry_Pharmaceuticals Biotechnology and Life Sciences industry_Real Estate Management and Development industry_Software and Services industry_Technology Hardware and Equipment industry_Telecommunication Services industry_Transportation industry_Utilities
0 AALI 2020 PT Astra Agro Lestari Tbk (IDX:AALI) 0 <NA> Food, Beverage and Tobacco 79.680000 2.100000 NaN 3.313000 1.322000 9 23 0.220417 0.213737 0.064522 0.035236 0.546102 0.421875 NaN 0.307166 0.210873 0.304362 0.692834 1.609740 0.638614 NaN 0.149215 0.061495 2.779863 0.676969 0.032172 0.046435 0.109109 0.159371 0.090839 0.047524 0.083588 0.272125 0.123473 0.021436 464.375964 9771.474517 1206.514302 17.139871 16.749742 16.981904 1.232442 0.077599 2.668607 0.029922 0.014188 0.796850 False False False False False False False False False True False False False False False False False False False False False False
1 AALI 2021 PT Astra Agro Lestari Tbk (IDX:AALI) 0 <NA> Food, Beverage and Tobacco 79.680000 2.100000 NaN 1.579000 0.752000 9 24 0.142003 0.309679 0.196066 0.128159 0.653652 0.359192 NaN 0.303578 0.191385 0.274811 0.696422 0.445443 0.584911 NaN 0.113613 0.100867 1.981262 0.800070 0.068006 0.097650 0.141938 0.200637 0.126072 0.085000 0.161024 0.530422 0.201263 0.095959 1074.128192 12636.876102 2543.330739 17.229950 17.006894 16.721566 0.863653 0.293241 1.313057 0.094261 0.099927 1.107999 False False False False False False False False False True False False False False False False False False False False False False
2 AALI 2022 PT Astra Agro Lestari Tbk (IDX:AALI) 0 <NA> Food, Beverage and Tobacco 79.680000 2.100000 NaN 3.600000 1.227000 9 25 0.244527 0.252676 0.070188 0.055373 0.788926 0.464748 NaN 0.239531 0.138593 0.182247 0.760469 0.656024 0.615273 NaN 0.182489 0.083667 2.204591 0.746293 0.061268 0.080566 0.126856 0.167314 0.112109 0.082096 0.062750 0.261971 0.084082 -0.077828 931.085812 11341.364015 953.607381 17.191368 16.898731 16.552836 0.694397 -0.102518 -0.133171 -0.037848 0.050637 -0.625056 False False False False False False False False False True False False False False False False False False False False False False
print('=== Dataset Fitur dengan Label Distress Ekuitas Negatif yang berukuran',df_fitur_negeq.shape,'===')
df_fitur_negeq.head(3)
=== Dataset Fitur dengan Label Distress Ekuitas Negatif yang berukuran (6403, 75) ===
ticker Year Entity Name target_distress tahun_distress Industry Group Parent Percent Owned (%) Percent Owned - All Institutions (%) Percent Owned - Insiders (%) CR QR Age_When_IPO Years_Since_IPO WC_Sales CA_TA CL_TA CashST_TA CashST_CL Inventory_CA Prepaid_CA TL_TA Debt_TA Debt_Equity Equity_TA NetDebt_EBITDA PPE_TA Intang_TA Altman_X1_WC_TA Altman_X3_EBIT_TA Altman_X4_MVE_TL Altman_X5_SalesTA_AssetTurnover ROA ROE EBITDA_TA GrossMargin OpMargin NetMargin CFO_TA CFO_TL CFO_Sales NetCash_TA EPS_proxy Sales_per_share CFO_per_share log_TA log_Sales log_MktCap PB Sales_growth NI_growth TA_growth Equity_growth CFO_growth industry_Banks industry_Capital Goods industry_Commercial and Professional Services industry_Consumer Discretionary Distribution and Retail industry_Consumer Durables and Apparel industry_Consumer Services industry_Consumer Staples Distribution and Retail industry_Energy industry_Financial Services industry_Food Beverage and Tobacco industry_Health Care Equipment and Services industry_Household and Personal Products industry_Insurance industry_Materials industry_Media and Entertainment industry_Pharmaceuticals Biotechnology and Life Sciences industry_Real Estate Management and Development industry_Software and Services industry_Technology Hardware and Equipment industry_Telecommunication Services industry_Transportation industry_Utilities
0 AALI 2008 PT Astra Agro Lestari Tbk (IDX:AALI) 0 <NA> Food, Beverage and Tobacco 79.680000 2.100000 NaN 1.944000 0.878000 9 11 0.117567 0.303024 0.155859 0.133083 0.853871 0.395495 0.018945 0.181481 0.000000 0.000000 0.818519 -0.239907 0.632481 NaN 0.147166 0.517036 13.042854 1.251761 0.416504 0.508850 0.554729 0.466033 0.413047 0.332734 0.320168 1.764201 0.255774 -0.022255 1724.417604 5182.564161 1325.566362 15.690353 15.914904 16.551986 2.891836 NaN NaN NaN NaN NaN False False False False False False False False False True False False False False False False False False False False False False
1 AALI 2009 PT Astra Agro Lestari Tbk (IDX:AALI) 0 <NA> Food, Beverage and Tobacco 79.680000 2.100000 NaN 1.826000 1.007000 9 12 0.104448 0.226435 0.124016 0.104148 0.839797 0.355822 0.021493 0.151198 0.000000 0.000000 0.848802 -0.272069 0.692401 NaN 0.102418 0.343831 31.294532 0.980570 0.228445 0.269138 0.382801 0.417789 0.350644 0.232972 0.262157 1.733860 0.267352 -0.010451 1098.367037 4714.593791 1260.454232 15.839888 15.820267 17.394169 5.574543 -0.090297 -0.363050 0.161295 0.204258 -0.049120 False False False False False False False False False True False False False False False False False False False False False False
2 AALI 2010 PT Astra Agro Lestari Tbk (IDX:AALI) 0 <NA> Food, Beverage and Tobacco 79.680000 2.100000 NaN 1.932000 1.262000 9 13 0.111868 0.233306 0.120778 0.141129 1.168507 0.304554 0.010879 0.151794 0.000000 0.000000 0.848206 -0.371938 0.694187 NaN 0.112528 0.340407 30.915714 1.005906 0.239274 0.282095 0.379443 0.408126 0.338409 0.237870 0.335160 2.207991 0.333192 0.051438 1335.868347 5615.970205 1871.196289 15.989330 15.995218 17.535363 5.532640 0.191189 0.216231 0.161186 0.160371 0.484541 False False False False False False False False False True False False False False False False False False False False False False
print('=== Dataset Fitur dengan Label Distress Rugi 2 Tahun Berturut-turut yang berukuran',df_fitur_conloss.shape,'===')
df_fitur_conloss.head(3)
=== Dataset Fitur dengan Label Distress Rugi 2 Tahun Berturut-turut yang berukuran (5160, 75) ===
ticker Year Entity Name target_distress tahun_distress Industry Group Parent Percent Owned (%) Percent Owned - All Institutions (%) Percent Owned - Insiders (%) CR QR Age_When_IPO Years_Since_IPO WC_Sales CA_TA CL_TA CashST_TA CashST_CL Inventory_CA Prepaid_CA TL_TA Debt_TA Debt_Equity Equity_TA NetDebt_EBITDA PPE_TA Intang_TA Altman_X1_WC_TA Altman_X3_EBIT_TA Altman_X4_MVE_TL Altman_X5_SalesTA_AssetTurnover ROA ROE EBITDA_TA GrossMargin OpMargin NetMargin CFO_TA CFO_TL CFO_Sales NetCash_TA EPS_proxy Sales_per_share CFO_per_share log_TA log_Sales log_MktCap PB Sales_growth NI_growth TA_growth Equity_growth CFO_growth industry_Banks industry_Capital Goods industry_Commercial and Professional Services industry_Consumer Discretionary Distribution and Retail industry_Consumer Durables and Apparel industry_Consumer Services industry_Consumer Staples Distribution and Retail industry_Energy industry_Financial Services industry_Food Beverage and Tobacco industry_Health Care Equipment and Services industry_Household and Personal Products industry_Insurance industry_Materials industry_Media and Entertainment industry_Pharmaceuticals Biotechnology and Life Sciences industry_Real Estate Management and Development industry_Software and Services industry_Technology Hardware and Equipment industry_Telecommunication Services industry_Transportation industry_Utilities
0 AALI 2008 PT Astra Agro Lestari Tbk (IDX:AALI) 0 <NA> Food, Beverage and Tobacco 79.680000 2.100000 NaN 1.944000 0.878000 9 11 0.117567 0.303024 0.155859 0.133083 0.853871 0.395495 0.018945 0.181481 0.000000 0.000000 0.818519 -0.239907 0.632481 NaN 0.147166 0.517036 13.042854 1.251761 0.416504 0.508850 0.554729 0.466033 0.413047 0.332734 0.320168 1.764201 0.255774 -0.022255 1724.417604 5182.564161 1325.566362 15.690353 15.914904 16.551986 2.891836 NaN NaN NaN NaN NaN False False False False False False False False False True False False False False False False False False False False False False
1 AALI 2009 PT Astra Agro Lestari Tbk (IDX:AALI) 0 <NA> Food, Beverage and Tobacco 79.680000 2.100000 NaN 1.826000 1.007000 9 12 0.104448 0.226435 0.124016 0.104148 0.839797 0.355822 0.021493 0.151198 0.000000 0.000000 0.848802 -0.272069 0.692401 NaN 0.102418 0.343831 31.294532 0.980570 0.228445 0.269138 0.382801 0.417789 0.350644 0.232972 0.262157 1.733860 0.267352 -0.010451 1098.367037 4714.593791 1260.454232 15.839888 15.820267 17.394169 5.574543 -0.090297 -0.363050 0.161295 0.204258 -0.049120 False False False False False False False False False True False False False False False False False False False False False False
2 AALI 2010 PT Astra Agro Lestari Tbk (IDX:AALI) 0 <NA> Food, Beverage and Tobacco 79.680000 2.100000 NaN 1.932000 1.262000 9 13 0.111868 0.233306 0.120778 0.141129 1.168507 0.304554 0.010879 0.151794 0.000000 0.000000 0.848206 -0.371938 0.694187 NaN 0.112528 0.340407 30.915714 1.005906 0.239274 0.282095 0.379443 0.408126 0.338409 0.237870 0.335160 2.207991 0.333192 0.051438 1335.868347 5615.970205 1871.196289 15.989330 15.995218 17.535363 5.532640 0.191189 0.216231 0.161186 0.160371 0.484541 False False False False False False False False False True False False False False False False False False False False False False
all_dfs = {'ppk': df_fitur_ppk, 'negeq': df_fitur_negeq, 'conloss': df_fitur_conloss}
all_dfs.keys()
dict_keys(['ppk', 'negeq', 'conloss'])

Modelling

# limit()

Split Train Test dan X y

# all_dfs = {'ppk': df_fitur_ppk, 'negeq': df_fitur_negeq, 'conloss': df_fitur_conloss}
# kolom_missing_indicator = {'ppk': ['fitur_A', ...], 'negeq': [...], 'conloss': [...]}

metadata_cols = ['ticker', 'Year', 'Entity Name', 'tahun_distress', 'Industry Group']
target_col = 'target_distress'

split_years = {
    'ppk': 2023,
    'negeq': 2020,
    'conloss': 2020
} # tahun disini & setelah-setelahnya masuk test

# Dictionary utama untuk menyimpan split data mentah
dict_data_splits = {}

for target_name, df_target in all_dfs.items():
    # data diurutkan berdasarkan tahun untuk mencegah leakage urutan
    df_target = df_target.sort_values(by=['Year', 'ticker']).reset_index(drop=True)
    
    year_limit = split_years[target_name]
    
    # Pisahkan X dan y (metadata masih ada di X)
    X = df_target.drop(columns=[target_col])
    y = df_target[target_col]
    
    # Split time-based
    train_mask = X['Year'] < year_limit
    test_mask = X['Year'] >= year_limit
    
    dict_data_splits[target_name] = {
        'X_train': X[train_mask].copy().reset_index(drop=True),
        'X_test': X[test_mask].copy().reset_index(drop=True),
        'y_train': y[train_mask].copy().reset_index(drop=True),
        'y_test': y[test_mask].copy().reset_index(drop=True)
    }
    print(f"[{target_name.upper()}] Train size: {len(X[train_mask])}, Test size: {len(X[test_mask])}")
[PPK] Train size: 1932, Test size: 651
[NEGEQ] Train size: 4615, Test size: 1788
[CONLOSS] Train size: 3933, Test size: 1227
def print_dict_structure(d, indent=0):
    for key, value in d.items():
        # Cetak key dengan spasi sesuai tingkatannya
        print('    ' * indent + f"├── {key}")
        
        # Jika isinya adalah dictionary lagi, telusuri lebih dalam
        if isinstance(value, dict):
            print_dict_structure(value, indent + 1)
        else:
            # Jika sudah mentok (misalnya isinya DataFrame), cetak tipe datanya
            print('    ' * (indent + 1) + f"└── (Isi: {type(value).__name__})")

print_dict_structure(dict_data_splits)
├── ppk
    ├── X_train
        └── (Isi: DataFrame)
    ├── X_test
        └── (Isi: DataFrame)
    ├── y_train
        └── (Isi: Series)
    ├── y_test
        └── (Isi: Series)
├── negeq
    ├── X_train
        └── (Isi: DataFrame)
    ├── X_test
        └── (Isi: DataFrame)
    ├── y_train
        └── (Isi: Series)
    ├── y_test
        └── (Isi: Series)
├── conloss
    ├── X_train
        └── (Isi: DataFrame)
    ├── X_test
        └── (Isi: DataFrame)
    ├── y_train
        └── (Isi: Series)
    ├── y_test
        └── (Isi: Series)
dict_data_splits['ppk']['X_test'].head(3)
ticker Year Entity Name tahun_distress Industry Group Parent Percent Owned (%) Percent Owned - All Institutions (%) Percent Owned - Insiders (%) CR QR Age_When_IPO Years_Since_IPO WC_Sales CA_TA CL_TA CashST_TA CashST_CL Inventory_CA Prepaid_CA TL_TA Debt_TA Debt_Equity Equity_TA NetDebt_EBITDA PPE_TA Intang_TA Altman_X1_WC_TA Altman_X3_EBIT_TA Altman_X4_MVE_TL Altman_X5_SalesTA_AssetTurnover ROA ROE EBITDA_TA GrossMargin OpMargin NetMargin CFO_TA CFO_TL CFO_Sales NetCash_TA EPS_proxy Sales_per_share CFO_per_share log_TA log_Sales log_MktCap PB Sales_growth NI_growth TA_growth Equity_growth CFO_growth industry_Banks industry_Capital Goods industry_Commercial and Professional Services industry_Consumer Discretionary Distribution and Retail industry_Consumer Durables and Apparel industry_Consumer Services industry_Consumer Staples Distribution and Retail industry_Energy industry_Financial Services industry_Food Beverage and Tobacco industry_Health Care Equipment and Services industry_Household and Personal Products industry_Insurance industry_Materials industry_Media and Entertainment industry_Pharmaceuticals Biotechnology and Life Sciences industry_Real Estate Management and Development industry_Software and Services industry_Technology Hardware and Equipment industry_Telecommunication Services industry_Transportation industry_Utilities
0 AALI 2023 PT Astra Agro Lestari Tbk (IDX:AALI) <NA> Food, Beverage and Tobacco 79.680000 2.100000 NaN 1.834000 0.765000 9 26 0.155989 0.246764 0.134580 0.072436 0.538236 0.438658 NaN 0.217714 0.138841 0.177482 0.782286 0.636827 0.627661 NaN 0.112183 0.057966 2.152934 0.719174 0.037723 0.048222 0.104275 0.134720 0.080600 0.052453 0.088009 0.404242 0.122376 0.016290 565.374654 10778.614202 1319.038494 17.177490 16.847839 16.419750 0.599173 -0.049619 -0.392779 -0.013781 0.014512 0.383209 False False False False False False False False False True False False False False False False False False False False False False
1 ABBA 2023 PT Mahaka Media Tbk (IDX:ABBA) 2024 Media and Entertainment 51.160000 NaN 0.010000 0.551000 0.377000 10 21 -0.510978 0.464570 0.842947 0.116017 0.137633 0.035783 0.014761 1.390180 0.751819 -1.926851 -0.390180 -4.774142 0.106254 0.056911 -0.378377 -0.169481 0.542985 0.740497 -0.193300 0.495411 -0.133176 0.290642 -0.228875 -0.261041 -0.215986 -0.155365 -0.291677 -0.158563 -13.316065 51.011484 -14.878874 12.510378 12.209944 12.229137 -1.934610 0.179525 0.692448 -0.302784 -4.596127 -0.391259 False False False False False False False False False False False False False False True False False False False False False False
2 ABDA 2023 PT Asuransi Bina Dana Arta Tbk (IDX:ABDA) 2025 Insurance 87.760000 NaN 0.000000 4.143000 3.843000 7 34 2.161418 0.871290 0.210323 0.764564 3.635184 NaN 0.000571 0.418694 0.000000 0.000000 0.581306 -24.397668 0.025011 NaN 0.660966 0.027747 3.227599 0.305802 0.031742 0.054605 0.031338 0.460030 0.090734 0.103800 -0.011484 -0.027428 -0.037554 0.104000 136.235201 1312.478131 -49.288315 14.795508 13.610692 15.096633 2.324728 0.046444 -0.072007 0.077806 0.028809 -0.165972 False False False False False False False False False False False False True False False False False False False False False False

Simpan Sebaran Fitur untuk Commentary

print_dict_structure(dict_data_splits['ppk'])
├── X_train
    └── (Isi: DataFrame)
├── X_test
    └── (Isi: DataFrame)
├── y_train
    └── (Isi: Series)
├── y_test
    └── (Isi: Series)
for indikator, dict_datasets in dict_data_splits.items():
    print(dict_datasets['X_train'].head(2))
  ticker  Year                           Entity Name  tahun_distress              Industry Group  Parent Percent Owned (%)  Percent Owned - All Institutions (%)  Percent Owned - Insiders (%)       CR       QR  \
0   AALI  2020  PT Astra Agro Lestari Tbk (IDX:AALI)            <NA>  Food, Beverage and Tobacco                 79.680000                              2.100000                           NaN 3.313000 1.322000   
1   ABBA  2020        PT Mahaka Media Tbk (IDX:ABBA)            2024     Media and Entertainment                 51.160000                                   NaN                      0.010000 0.395000 0.331000   

   Age_When_IPO  Years_Since_IPO  WC_Sales    CA_TA    CL_TA  CashST_TA  CashST_CL  Inventory_CA  Prepaid_CA    TL_TA  Debt_TA  Debt_Equity  Equity_TA  NetDebt_EBITDA   PPE_TA  Intang_TA  Altman_X1_WC_TA  \
0             9               23  0.220417 0.213737 0.064522   0.035236   0.546102      0.421875         NaN 0.307166 0.210873     0.304362   0.692834        1.609740 0.638614        NaN         0.149215   
1            10               18 -0.493552 0.230787 0.583961   0.120570   0.206470      0.091745    0.009467 1.467895 0.799858    -1.709483  -0.467895       -3.523342 0.125893   0.098273        -0.353174   

   Altman_X3_EBIT_TA  Altman_X4_MVE_TL  Altman_X5_SalesTA_AssetTurnover       ROA      ROE  EBITDA_TA  GrossMargin  OpMargin  NetMargin    CFO_TA    CFO_TL  CFO_Sales  NetCash_TA  EPS_proxy  Sales_per_share  \
0           0.061495          2.779863                         0.676969  0.032172 0.046435   0.109109     0.159371  0.090839   0.047524  0.083588  0.272125   0.123473    0.021436 464.375964      9771.474517   
1          -0.236487          0.694370                         0.715576 -0.263170 0.562456  -0.192796     0.456651 -0.330485  -0.367774 -0.227670 -0.155099  -0.318163    0.015107 -21.172133        57.568309   

   CFO_per_share    log_TA  log_Sales  log_MktCap        PB  Sales_growth  NI_growth  TA_growth  Equity_growth  CFO_growth  industry_Banks  industry_Capital Goods  industry_Commercial and Professional Services  \
0    1206.514302 17.139871  16.749742   16.981904  1.232442      0.077599   2.668607   0.029922       0.014188    0.796850           False                   False                                          False   
1     -18.316095 12.308858  11.974190   12.327937 -2.178401     -0.368317   0.317128  -0.463199      -2.126583    3.146363           False                   False                                          False   

   industry_Consumer Discretionary Distribution and Retail  industry_Consumer Durables and Apparel  industry_Consumer Services  industry_Consumer Staples Distribution and Retail  industry_Energy  \
0                                              False                                         False                       False                                              False            False   
1                                              False                                         False                       False                                              False            False   

   industry_Financial Services  industry_Food Beverage and Tobacco  industry_Health Care Equipment and Services  industry_Household and Personal Products  industry_Insurance  industry_Materials  \
0                        False                                True                                        False                                     False               False               False   
1                        False                               False                                        False                                     False               False               False   

   industry_Media and Entertainment  industry_Pharmaceuticals Biotechnology and Life Sciences  industry_Real Estate Management and Development  industry_Software and Services  \
0                             False                                              False                                                   False                           False   
1                              True                                              False                                                   False                           False   

   industry_Technology Hardware and Equipment  industry_Telecommunication Services  industry_Transportation  industry_Utilities  
0                                       False                                False                    False               False  
1                                       False                                False                    False               False  
  ticker  Year                           Entity Name  tahun_distress              Industry Group  Parent Percent Owned (%)  Percent Owned - All Institutions (%)  Percent Owned - Insiders (%)       CR       QR  \
0   AALI  2008  PT Astra Agro Lestari Tbk (IDX:AALI)            <NA>  Food, Beverage and Tobacco                 79.680000                              2.100000                           NaN 1.944000 0.878000   
1   ABBA  2008        PT Mahaka Media Tbk (IDX:ABBA)            2020     Media and Entertainment                 51.160000                                   NaN                      0.010000 2.188000 1.794000   

   Age_When_IPO  Years_Since_IPO  WC_Sales    CA_TA    CL_TA  CashST_TA  CashST_CL  Inventory_CA  Prepaid_CA    TL_TA  Debt_TA  Debt_Equity  Equity_TA  NetDebt_EBITDA   PPE_TA  Intang_TA  Altman_X1_WC_TA  \
0             9               11  0.117567 0.303024 0.155859   0.133083   0.853871      0.395495    0.018945 0.181481 0.000000     0.000000   0.818519       -0.239907 0.632481        NaN         0.147166   
1            10                6  0.373814 0.437741 0.200019   0.081366   0.406792      0.016806    0.007812 0.288351 0.071997     0.101169   0.711649       -0.101827 0.222390        NaN         0.237722   

   Altman_X3_EBIT_TA  Altman_X4_MVE_TL  Altman_X5_SalesTA_AssetTurnover      ROA      ROE  EBITDA_TA  GrossMargin  OpMargin  NetMargin   CFO_TA   CFO_TL  CFO_Sales  NetCash_TA   EPS_proxy  Sales_per_share  \
0           0.517036         13.042854                         1.251761 0.416504 0.508850   0.554729     0.466033  0.413047   0.332734 0.320168 1.764201   0.255774   -0.022255 1724.417604      5182.564161   
1           0.048964          3.051410                         0.635936 0.016276 0.022872   0.092011     0.425306  0.076994   0.025595 0.005176 0.017950   0.008139    0.063200    2.589808       101.185871   

   CFO_per_share    log_TA  log_Sales  log_MktCap       PB  Sales_growth  NI_growth  TA_growth  Equity_growth  CFO_growth  industry_Banks  industry_Capital Goods  industry_Commercial and Professional Services  \
0    1325.566362 15.690353  15.914904   16.551986 2.891836           NaN        NaN        NaN            NaN         NaN           False                   False                                          False   
1       0.823541 12.329436  11.876779   12.201462 1.236390           NaN        NaN        NaN            NaN         NaN           False                   False                                          False   

   industry_Consumer Discretionary Distribution and Retail  industry_Consumer Durables and Apparel  industry_Consumer Services  industry_Consumer Staples Distribution and Retail  industry_Energy  \
0                                              False                                         False                       False                                              False            False   
1                                              False                                         False                       False                                              False            False   

   industry_Financial Services  industry_Food Beverage and Tobacco  industry_Health Care Equipment and Services  industry_Household and Personal Products  industry_Insurance  industry_Materials  \
0                        False                                True                                        False                                     False               False               False   
1                        False                               False                                        False                                     False               False               False   

   industry_Media and Entertainment  industry_Pharmaceuticals Biotechnology and Life Sciences  industry_Real Estate Management and Development  industry_Software and Services  \
0                             False                                              False                                                   False                           False   
1                              True                                              False                                                   False                           False   

   industry_Technology Hardware and Equipment  industry_Telecommunication Services  industry_Transportation  industry_Utilities  
0                                       False                                False                    False               False  
1                                       False                                False                    False               False  
  ticker  Year                           Entity Name  tahun_distress              Industry Group  Parent Percent Owned (%)  Percent Owned - All Institutions (%)  Percent Owned - Insiders (%)       CR       QR  \
0   AALI  2008  PT Astra Agro Lestari Tbk (IDX:AALI)            <NA>  Food, Beverage and Tobacco                 79.680000                              2.100000                           NaN 1.944000 0.878000   
1   ABBA  2008        PT Mahaka Media Tbk (IDX:ABBA)            2016     Media and Entertainment                 51.160000                                   NaN                      0.010000 2.188000 1.794000   

   Age_When_IPO  Years_Since_IPO  WC_Sales    CA_TA    CL_TA  CashST_TA  CashST_CL  Inventory_CA  Prepaid_CA    TL_TA  Debt_TA  Debt_Equity  Equity_TA  NetDebt_EBITDA   PPE_TA  Intang_TA  Altman_X1_WC_TA  \
0             9               11  0.117567 0.303024 0.155859   0.133083   0.853871      0.395495    0.018945 0.181481 0.000000     0.000000   0.818519       -0.239907 0.632481        NaN         0.147166   
1            10                6  0.373814 0.437741 0.200019   0.081366   0.406792      0.016806    0.007812 0.288351 0.071997     0.101169   0.711649       -0.101827 0.222390        NaN         0.237722   

   Altman_X3_EBIT_TA  Altman_X4_MVE_TL  Altman_X5_SalesTA_AssetTurnover      ROA      ROE  EBITDA_TA  GrossMargin  OpMargin  NetMargin   CFO_TA   CFO_TL  CFO_Sales  NetCash_TA   EPS_proxy  Sales_per_share  \
0           0.517036         13.042854                         1.251761 0.416504 0.508850   0.554729     0.466033  0.413047   0.332734 0.320168 1.764201   0.255774   -0.022255 1724.417604      5182.564161   
1           0.048964          3.051410                         0.635936 0.016276 0.022872   0.092011     0.425306  0.076994   0.025595 0.005176 0.017950   0.008139    0.063200    2.589808       101.185871   

   CFO_per_share    log_TA  log_Sales  log_MktCap       PB  Sales_growth  NI_growth  TA_growth  Equity_growth  CFO_growth  industry_Banks  industry_Capital Goods  industry_Commercial and Professional Services  \
0    1325.566362 15.690353  15.914904   16.551986 2.891836           NaN        NaN        NaN            NaN         NaN           False                   False                                          False   
1       0.823541 12.329436  11.876779   12.201462 1.236390           NaN        NaN        NaN            NaN         NaN           False                   False                                          False   

   industry_Consumer Discretionary Distribution and Retail  industry_Consumer Durables and Apparel  industry_Consumer Services  industry_Consumer Staples Distribution and Retail  industry_Energy  \
0                                              False                                         False                       False                                              False            False   
1                                              False                                         False                       False                                              False            False   

   industry_Financial Services  industry_Food Beverage and Tobacco  industry_Health Care Equipment and Services  industry_Household and Personal Products  industry_Insurance  industry_Materials  \
0                        False                                True                                        False                                     False               False               False   
1                        False                               False                                        False                                     False               False               False   

   industry_Media and Entertainment  industry_Pharmaceuticals Biotechnology and Life Sciences  industry_Real Estate Management and Development  industry_Software and Services  \
0                             False                                              False                                                   False                           False   
1                              True                                              False                                                   False                           False   

   industry_Technology Hardware and Equipment  industry_Telecommunication Services  industry_Transportation  industry_Utilities  
0                                       False                                False                    False               False  
1                                       False                                False                    False               False  
import json

# rasio yang interpretable & actionable untuk user manajemen/investor
# arah: "high_good" = makin tinggi makin sehat, "low_good" = makin rendah makin sehat
commentary_ratios = {
    "TL_TA": "low_good", "Debt_Equity": "low_good", "Equity_TA": "high_good",
    "NetDebt_EBITDA": "low_good",
    "CR": "high_good", "QR": "high_good", "CashST_CL": "high_good",
    "ROA": "high_good", "ROE": "high_good", "NetMargin": "high_good",
    "OpMargin": "high_good", "GrossMargin": "high_good",
    "CFO_TA": "high_good", "CFO_Sales": "high_good",
    "Sales_growth": "high_good", "NI_growth": "high_good",
}

# train_sets = {"loss_two_years": (X_train, y_train), "negative_equity": (...), "watchlist_board": (...)}
out = {}
for indicator, dict_datasets in dict_data_splits.items():
    Xtr = dict_datasets['X_train']
    ytr = dict_datasets['y_train']
    d = Xtr.copy()
    d["_y"] = ytr.values
    stats = {}
    for r, direction in commentary_ratios.items():
        if r not in d.columns:
            continue
        healthy = d.loc[d["_y"] == 0, r].dropna()
        stats[r] = {
            "direction": direction,
            "healthy_p25": float(healthy.quantile(.25)),
            "healthy_median": float(healthy.median()),
            "healthy_p75": float(healthy.quantile(.75)),
        }
    out[indicator] = stats

json.dump(out, open("commentary_stats.json", "w"), indent=2)

Inisialisasi dan Konfigurasi Model

Konfigurasi Variabel, Model, dan Compute Type

# List konfigurasi
targets = ['ppk', 'negeq', 'conloss']
null_strategies = ['keep', 'impute']
feat_strategies = ['all', 'corr', 'pca']
# # NOTE : CONFIG / KONFIGURASI !!!
# compute_type = 'gpu'
## ((sudah dipindah di paling awal notebook))

if compute_type == 'cpu':
    model_names = ['RandomForest', 'AdaBoost']
    n_jobs_optuna = -1  #  seluruh core dipakai untuk paralel optuna
else: # gpu
    model_names = ['XGBoost', 'LightGBM', 'CatBoost']
    n_jobs_optuna = 1   # DILARANG paralel agar VRAM GPU tidak crash
# Dictionary untuk menyimpan hasil akhir Optuna dan CV
dict_master_results = {}

Fungsi Cross Validation

def get_custom_cv_indices(X_train_df):
    """
    Menghasilkan list tuple (train_indices, val_indices) berdasarkan window-expanding 'Year'.
    """
    years = sorted(X_train_df['Year'].unique())
    cv_indices = []
    
    # Mulai dari tahun kedua agar fold pertama punya minimal 1 tahun train
    for i in range(1, len(years)):
        val_year = years[i]
        
        # Train: Semua tahun sebelum val_year
        train_idx = X_train_df.index[X_train_df['Year'] < val_year].tolist()
        # Validation: Hanya tahun val_year
        val_idx = X_train_df.index[X_train_df['Year'] == val_year].tolist()
        
        cv_indices.append((train_idx, val_idx))
        
    return cv_indices

Fungsi Imputasi dan Low Corr Feat Selection

def apply_custom_imputation(df_train, df_test, fitur_cols):
    # 1. GABUNGKAN SEMENTARA (Temporal Concat)
    # Tujuannya agar FFILL bisa mengalir mulus dari akhir Train ke awal Test, 
    # dan juga mengalir di dalam Test itu sendiri.
    df_train_temp = df_train.copy()
    df_test_temp = df_test.copy()
    
    df_train_temp['is_test'] = 0
    df_test_temp['is_test'] = 1
    
    combined = pd.concat([df_train_temp, df_test_temp], axis=0)
    
    for col in fitur_cols:
        combined[col] = pd.to_numeric(combined[col], errors='coerce').astype(float)
        
    combined = combined.sort_values(by=['ticker', 'Year'])
    
    # 2. LOGIKA FFILL YANG KETAT (Mencegah loncat jika baris tahun bolong)
    # Buat dataframe bergeser (shift 1 ke bawah) per ticker
    shifted = combined.groupby('ticker')[fitur_cols + ['Year']].shift(1)
    
    # Masking: Cek apakah baris sebelumnya persis berjarak 1 tahun
    is_gap_one_year = ((combined['Year'] - shifted['Year']) == 1).fillna(False).astype(bool)
    
    # Lakukan ffill hanya pada sel yang jaraknya 1 tahun
    for col in fitur_cols:
        # Jika nilai sekarang NaN, dan jarak dengan baris sebelumnya 1 tahun, 
        # maka ambil nilai baris sebelumnya. Jika tidak, biarkan tetap NaN.
        combined[col] = np.where(
            combined[col].isna() & is_gap_one_year, 
            shifted[col], 
            combined[col]
        )
        
    # 3. PISAHKAN KEMBALI TRAIN DAN TEST
    train_imp = combined[combined['is_test'] == 0].drop(columns=['is_test']).copy()
    test_imp = combined[combined['is_test'] == 1].drop(columns=['is_test']).copy()
    
    # 4. EXTREME VALUE IMPUTATION (cuma dari train untuk mencegah Leakage)
    for col in fitur_cols:
        min_train = train_imp[col].min()
        if pd.isna(min_train):
            min_train = 0.0

        std_train = train_imp[col].std()
        if pd.isna(std_train) or std_train == 0:
            std_train = 1.0
            
        # Rumus custom nilai ekstrim menyesuaikan distribusi data
        extreme_val = min_train - (std_train * 10) - 100000.0
        
        train_imp[col] = train_imp[col].fillna(extreme_val)
        test_imp[col] = test_imp[col].fillna(extreme_val)
        
    return train_imp, test_imp

#######

# def apply_custom_imputation(df_train, df_test, fitur_cols):
#     """
#     Fungsi untuk melakukan imputasi custom: FFILL -> Group Median -> Global Median.
#     Proses 'fit' (perhitungan median) murni hanya dilakukan pada df_train untuk mencegah leakage.
#     """
    
#     # Gunakan copy agar tidak mengubah dataframe asli di luar fungsi (mencegah SettingWithCopyWarning)
#     train_imp = df_train.copy()
#     test_imp = df_test.copy()

#     # ---> [PERBAIKAN ERROR INT64 & NATYPE] <---
#     # Lakukan casting tipe data ke FLOAT paling awal sebelum operasi matematis apapun
#     for col in fitur_cols:
#         train_imp[col] = pd.to_numeric(train_imp[col], errors='coerce').astype(float)
#         test_imp[col] = pd.to_numeric(test_imp[col], errors='coerce').astype(float)
    
#     # --- TAHAP 1: Forward Fill (Berdasarkan Ticker) ---
#     # Pastikan Train terurut berdasarkan waktu
#     train_imp = train_imp.sort_values(by=['ticker', 'Year'])
#     train_imp[fitur_cols] = train_imp.groupby('ticker')[fitur_cols].ffill()
    
#     # Untuk Test/Val, kita ambil nilai TERAKHIR dari Train untuk tiap ticker (Mencegah leakage)
#     last_known_train = train_imp.groupby('ticker')[fitur_cols].last()
#     for col in fitur_cols:
#         test_imp[col] = test_imp[col].fillna(test_imp['ticker'].map(last_known_train[col]))
        
#     # --- TAHAP 2: Group Median (Industry Group + Size Bin) ---
#     # Binning log_TA (Membagi ukuran perusahaan menjadi Small, Medium, Large)
#     # Pastikan nama kolom 'log_TA' sesuai dengan nama asli di datasetmu
#     bins = pd.qcut(train_imp['log_TA'], q=3, retbins=True, duplicates='drop')[1]
#     bins[0], bins[-1] = -np.inf, np.inf # Perlebar batas untuk antisipasi nilai ekstrem di Test
    
#     train_imp['Size_Bin'] = pd.cut(train_imp['log_TA'], bins=bins, labels=['Small', 'Medium', 'Large'])
#     test_imp['Size_Bin'] = pd.cut(test_imp['log_TA'], bins=bins, labels=['Small', 'Medium', 'Large'])
    
#     # Hitung median grup HANYA dari Train
#     group_medians = train_imp.groupby(['Industry Group', 'Size_Bin'], observed=False)[fitur_cols].median()
    
#     # Eksekusi pengisian di Train
#     train_imp[fitur_cols] = train_imp.groupby(['Industry Group', 'Size_Bin'], observed=False)[fitur_cols].transform(lambda x: x.fillna(x.median()))
    
#     # Eksekusi pengisian di Test (Mapping dari tabel group_medians milik Train)
#     val_keys = pd.MultiIndex.from_arrays([test_imp['Industry Group'], test_imp['Size_Bin']])
#     mapped_medians = group_medians.reindex(val_keys).values
#     test_imp[fitur_cols] = test_imp[fitur_cols].fillna(pd.DataFrame(mapped_medians, index=test_imp.index, columns=fitur_cols))
    
#     # --- TAHAP 3: Global Median (Fallback) ---
#     # Berjaga-jaga jika ada grup yang isinya kosong semua sehingga mediannya tetap NaN
#     global_medians = train_imp[fitur_cols].median()
#     train_imp[fitur_cols] = train_imp[fitur_cols].fillna(global_medians)
#     test_imp[fitur_cols] = test_imp[fitur_cols].fillna(global_medians)

#     # --->  TAHAP 4 PERBAIKAN ERROR PCA (Extreme Fallback) <---
#     # Jika fitur 100% kosong, median global juga NaN. Kita paksa jadi 0 agar PCA tidak crash.
#     train_imp[fitur_cols] = train_imp[fitur_cols].fillna(0)
#     test_imp[fitur_cols] = test_imp[fitur_cols].fillna(0)
    
#     return train_imp, test_imp

def get_low_corr_features_target_aware(X_train_fold, y_train_fold, threshold=0.85):
    X_num = X_train_fold.select_dtypes(include=[np.number])

    # Cari kolom konstan
    constant_cols = [col for col in X_num.columns if X_num[col].nunique() <= 1]
    # Buang dari perhitungan korelasi agar tidak ada warning (tidak terpakai juga)
    X_num = X_num.drop(columns=constant_cols)

    # Masukkan fitur konstan ke dalam daftar buang agar ikut dihapus dari dataset akhir
    to_drop = set(constant_cols)

    # Hitung korelasi antar fitur tersisa dan thd target
    with warnings.catch_warnings():
        warnings.simplefilter("ignore") # Abaikan semua warning di dalam blok ini
        corr_matrix = X_num.corr(method='spearman').abs()
        target_corr = X_num.apply(lambda col: col.corr(y_train_fold, method='spearman')).abs()

    cols = corr_matrix.columns
    
    for i in range(len(cols)):
        for j in range(i+1, len(cols)):
            col_i, col_j = cols[i], cols[j]
            # Lewati jika salah satu sudah masuk daftar drop
            if col_i in to_drop or col_j in to_drop:
                continue
                
            # cek korelasi antar fitur yg melebihi threshold
            if corr_matrix.loc[col_i, col_j] > threshold:
                # perbaikan jika target_corr bernilai NaN maka dianggap 0 (tidak berkorelasi dengan target)
                corr_i = target_corr[col_i] if pd.notna(target_corr[col_i]) else 0
                corr_j = target_corr[col_j] if pd.notna(target_corr[col_j]) else 0
                
                # Bandingkan korelasi terhadap target, drop yang lebih kecil
                if corr_i > corr_j:
                    to_drop.add(col_j)
                else:
                    to_drop.add(col_i)
                    
    return list(to_drop)

# def get_low_corr_features(X_train_fold, threshold=0.85):
#     """
#     Mencari fitur yang harus di-drop agar tidak ada fitur yang saling berkorelasi > threshold.
#     Metadata diabaikan dalam perhitungan.
#     """
#     # Ambil hanya kolom numerik dan abaikan metadata
#     X_num = X_train_fold.drop(columns=metadata_cols, errors='ignore').select_dtypes(include=[np.number])
#     corr_matrix = X_num.corr().abs()
    
#     # Ambil segitiga atas dari matriks korelasi
#     upper = corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k=1).astype(bool))
    
#     # Cari nama kolom yang korelasinya di atas threshold
#     to_drop = [column for column in upper.columns if any(upper[column] > threshold)]
#     return to_drop

Fungsi Objective Optuna untuk Hyperparameter Tuning

# --- DEFINISI FUNGSI OPTUNA ---
def objective(trial, m_name, n_strat, f_strat, X_train_full, y_train_full, cv_indices):
    # Hyperparameter Space Search
    if m_name == 'RandomForest':
        params = {
            # 'n_estimators': trial.suggest_int('n_estimators', 50, 300),
            # 'max_depth': trial.suggest_int('max_depth', 3, 15),
            # 'class_weight': 'balanced',
            # 'n_jobs': -1,
            'random_state': 42,
            'n_estimators': trial.suggest_int('n_estimators', 100, 500),
            'max_depth': trial.suggest_int('max_depth', 5, 20),
            'min_samples_split': trial.suggest_int('min_samples_split', 2, 10),
            'min_samples_leaf': trial.suggest_int('min_samples_leaf', 1, 10),
            'max_features': trial.suggest_categorical('max_features', ['sqrt', 'log2', None]),
            'n_jobs': -1 # Memaksimalkan 4 core CPU kaggle
        }
        model = RandomForestClassifier(**params)
    elif m_name == 'XGBoost':
        params = {
            # 'n_estimators': trial.suggest_int('n_estimators', 50, 300),
            # 'max_depth': trial.suggest_int('max_depth', 3, 10),
            # 'learning_rate': trial.suggest_float('learning_rate', 1e-3, 0.3, log=True),
            # 'scale_pos_weight': trial.suggest_float('scale_pos_weight', 1, 20), # Berguna untuk imbalance
            # 'tree_method': 'hist',
            # 'device': 'cuda', # GPU
            'random_state': 42,
            'n_estimators': trial.suggest_int('n_estimators', 100, 1000),
            'max_depth': trial.suggest_int('max_depth', 3, 7), # Jangan lebih dari 7 untuk data <10k baris
            'learning_rate': trial.suggest_float('learning_rate', 1e-3, 0.1, log=True),
            'subsample': trial.suggest_float('subsample', 0.6, 1.0),
            'colsample_bytree': trial.suggest_float('colsample_bytree', 0.6, 1.0),
            'gamma': trial.suggest_float('gamma', 0, 5), # Penalti kompleksitas node (penting!)
            'reg_alpha': trial.suggest_float('reg_alpha', 1e-3, 10.0, log=True), # L1 Regularization
            'reg_lambda': trial.suggest_float('reg_lambda', 1e-3, 10.0, log=True), # L2 Regularization
            'tree_method': 'hist',
            'device': 'cuda' # GPU
        }
        model = XGBClassifier(**params)
    elif m_name == 'LightGBM':
        params = {
            # 'n_estimators': trial.suggest_int('n_estimators', 50, 300),
            # 'max_depth': trial.suggest_int('max_depth', 3, 15),
            # 'learning_rate': trial.suggest_float('learning_rate', 1e-3, 0.3, log=True),
            # 'class_weight': 'balanced',
            # 'device_type': 'gpu', # Gunakan GPU
            'random_state': 42,
            'verbose': -1,
            'n_estimators': trial.suggest_int('n_estimators', 100, 1000),
            'num_leaves': trial.suggest_int('num_leaves', 15, 63), # Rule of thumb: < 2^max_depth
            'max_depth': trial.suggest_int('max_depth', 3, 7),
            'learning_rate': trial.suggest_float('learning_rate', 1e-3, 0.1, log=True),
            'min_child_samples': trial.suggest_int('min_child_samples', 10, 50), # Kunci anti-overfitting
            'subsample': trial.suggest_float('subsample', 0.6, 1.0),
            'colsample_bytree': trial.suggest_float('colsample_bytree', 0.6, 1.0),
            'reg_alpha': trial.suggest_float('reg_alpha', 1e-3, 10.0, log=True),
            'reg_lambda': trial.suggest_float('reg_lambda', 1e-3, 10.0, log=True),
            'device': 'gpu'
        }
        model = LGBMClassifier(**params)
    elif m_name == 'AdaBoost':
        params = {
            # 'n_estimators': trial.suggest_int('n_estimators', 50, 300),
            # 'learning_rate': trial.suggest_float('learning_rate', 1e-3, 0.5, log=True),
            'random_state': 42,
            'n_estimators': trial.suggest_int('n_estimators', 50, 300),
            'learning_rate': trial.suggest_float('learning_rate', 1e-3, 1.0, log=True),
        }
        model = AdaBoostClassifier(**params)
    elif m_name == 'CatBoost':
        params = {
            # 'iterations': trial.suggest_int('iterations', 50, 300),
            # 'depth': trial.suggest_int('depth', 3, 10),
            # 'learning_rate': trial.suggest_float('learning_rate', 1e-3, 0.3, log=True),
            # 'auto_class_weights': 'Balanced',
            # 'task_type': 'GPU', # Gunakan GPU
            # 'verbose': 0,
            'random_seed': 42,
            'iterations': trial.suggest_int('iterations', 100, 500),
            'depth': trial.suggest_int('depth', 3, 6),
            'learning_rate': trial.suggest_float('learning_rate', 5e-3, 0.2, log=True),
            'l2_leaf_reg': trial.suggest_float('l2_leaf_reg', 0.1, 10.0), # CatBoost sangat sensitif pada L2
            'border_count': trial.suggest_int('border_count', 32, 128),
            'task_type': 'GPU',
            'verbose': 0
        }
        model = CatBoostClassifier(**params)

    # 2. Proses Custom CV tiap fold
    fold_pr_auc = []
    
    for train_idx, val_idx in cv_indices:
        # Split data di fold ini
        X_tr_fold = X_train_full.iloc[train_idx].copy()
        X_val_fold = X_train_full.iloc[val_idx].copy()
        y_tr_fold = y_train_full.iloc[train_idx].copy()
        y_val_fold = y_train_full.iloc[val_idx].copy()
        
        # -- TAHAP PREPROCESSING DI DALAM FOLD --

        # A. Imputasi
        if n_strat == 'impute':
            # Ambil kolom fitur saja (tanpa metadata)
            fitur_cols = [c for c in X_tr_fold.columns if c not in metadata_cols]
            
            # Panggil fungsi custom imputation
            X_tr_fold, X_val_fold = apply_custom_imputation(
                df_train=X_tr_fold, 
                df_test=X_val_fold, 
                fitur_cols=fitur_cols
            )

        # Penyimpanan metadata untuk nanti digabung kembali jika diperlukan (misal untuk PCA yang butuh fitur asli)
        meta_tr = X_tr_fold[metadata_cols].reset_index(drop=True)
        meta_val = X_val_fold[metadata_cols].reset_index(drop=True)
            
        # B. Feature Strategy (Semua fitur yang ada sudah dipastikan tidak null di sini jika strat impute)
        if f_strat == 'corr':
            cols_to_drop = get_low_corr_features_target_aware(X_tr_fold, y_tr_fold, threshold=0.85)
            X_tr_fold = X_tr_fold.drop(columns=cols_to_drop)
            X_val_fold = X_val_fold.drop(columns=cols_to_drop)
        
        elif f_strat == 'pca':
            fitur_cols = [c for c in X_tr_fold.columns if c not in metadata_cols]

            # --- Buang Fitur Konstan Sebelum PCA ---
            constant_cols = [col for col in fitur_cols if X_tr_fold[col].nunique() <= 1]
            if constant_cols:
                X_tr_fold = X_tr_fold.drop(columns=constant_cols)
                X_val_fold = X_val_fold.drop(columns=constant_cols)
                # Perbarui daftar fitur setelah kolom konstan dibuang
                fitur_cols = [c for c in fitur_cols if c not in constant_cols]

            scaler = StandardScaler()
            pca = PCA(n_components=0.95, random_state=42) # Retain 95% variance
            
            # Scaler & PCA
            X_tr_pca = pca.fit_transform(scaler.fit_transform(X_tr_fold[fitur_cols]))
            X_val_pca = pca.transform(scaler.transform(X_val_fold[fitur_cols]))
            
            # # Rekonstruksi DataFrame (simpan kembali metadata)
            # meta_tr = X_tr_fold[metadata_cols].reset_index(drop=True)
            # meta_val = X_val_fold[metadata_cols].reset_index(drop=True)
            ### Sudah dipindah di atas ###
            
            pca_cols = [f"PC_{i}" for i in range(X_tr_pca.shape[1])]
            X_tr_fold = pd.concat([meta_tr, pd.DataFrame(X_tr_pca, columns=pca_cols)], axis=1)
            X_val_fold = pd.concat([meta_val, pd.DataFrame(X_val_pca, columns=pca_cols)], axis=1)

        # C. Drop Metadata Sesaat Sebelum Training
        X_tr_final = X_tr_fold.drop(columns=metadata_cols, errors='ignore')
        X_val_final = X_val_fold.drop(columns=metadata_cols, errors='ignore')
        
        # D. Training & Scoring
        model.fit(X_tr_final, y_tr_fold)
        
        # Ambil probabilitas kelas positif (index 1)
        y_val_probs = model.predict_proba(X_val_final)[:, 1]
        
        # Hitung PR-AUC (Average Precision)
        fold_score = average_precision_score(y_val_fold, y_val_probs)
        fold_pr_auc.append(fold_score)
        
    # Return rata-rata PR-AUC dari seluruh CV window
    return np.mean(fold_pr_auc)

Model Training (Find Best Params)

# limit()
# import warnings
# warnings.filterwarnings('ignore') # Matikan warning untuk output yang bersih

# =====================================================================
# KONFIGURASI BACKUP
# =====================================================================
# True  : Lanjutkan eksperimen dari file pickle yang ada di GDrive.
# False : Mulai dari awal (WARNING: Akan menimpa file backup di GDrive!)
resume_from_backup = True
# =====================================================================

backup_file_path = prefix_path + f'pickle/eksperimen_skripsi_backup_{compute_type}.pkl'

if resume_from_backup:
    if os.path.exists(backup_file_path):
        with open(backup_file_path, 'rb') as f:
            dict_master_results = pickle.load(f)
        print(f"✅ Berhasil me-load backup {compute_type.upper()}!")
        print(f"Terdapat {len(dict_master_results)} eksperimen yang sudah selesai.")
        print("Sistem akan melewati eksperimen tersebut dan melanjutkan sisanya...\n")
    else:
        print(f"⚠️ File backup tidak ditemukan di {backup_file_path}")
        print("Memulai dict_master_results kosong...\n")
        dict_master_results = {}
else:
    print("⚠️ Memulai ulang eksperimen dari AWAL (dict_master_results kosong).")
    print("File backup di GDrive akan ditimpa saat eksperimen pertama selesai!\n")
    dict_master_results = {}


# NESTED LOOP SEMUA KONFIGURASI TRAINING
for t_name in targets:
    # Ambil data spesifik target ini
    X_train_full = dict_data_splits[t_name]['X_train'].copy()
    y_train_full = dict_data_splits[t_name]['y_train'].copy()
    
    # Ekstrak indeks Custom CV
    cv_indices = get_custom_cv_indices(X_train_full)
    
    for n_strat in null_strategies:
        # pembuatan missing indicator
        # di luar fold
        # dan hanya jika strateginya impute
        if n_strat == 'impute':
            cols_to_flag = kolom_missing_indicator[t_name]
            for col in cols_to_flag:
                # tambah missing indicator (astype int agar jadi 1/0)
                X_train_full[f"{col}_is_missing"] = X_train_full[col].isnull().astype(int)
        
        for f_strat in feat_strategies:
            # PCA tidak bisa pakai data null, jadi diloncati
            if f_strat == 'pca' and n_strat == 'keep':
                continue # loncat ke next loop
                
            for m_name in model_names:
                # loncati model yang tidak bisa menerima data null
                if n_strat == 'keep' and m_name in ['AdaBoost']:
                    continue
                
                exp_id = f"{t_name}_{n_strat}_{f_strat}_{m_name}"

                # ---> [TAMBAHAN AGAR TIDAK MENGULANG DARI AWAL] <---
                if exp_id in dict_master_results:
                    print(f"Melewati {exp_id} karena sudah selesai dan tersimpan.")
                    continue


                print(f"Mulai eksperimen: {exp_id}")
                

                # --- JALANKAN OPTUNA ---
                study = optuna.create_study(direction='maximize')
                study.optimize(lambda trial: objective(trial, m_name, n_strat, f_strat, X_train_full, y_train_full, cv_indices), n_trials=100, n_jobs=n_jobs_optuna)
                
                # Simpan Hasil ke Master Dictionary
                dict_master_results[exp_id] = {
                    'best_params': study.best_params,
                    'best_cv_pr_auc': study.best_value
                }

                # ---> [TAMBAHAN MODIFIKASI 1: AUTO-SAVE] <---
                with open(save_path + f'pickle/eksperimen_skripsi_backup_{compute_type}.pkl', 'wb') as f:
                    pickle.dump(dict_master_results, f)
                print(f"Eksperimen {exp_id} berhasil di-save ke backup {compute_type}!")
                
# Contoh melihat hasil
print("\n=== EKSPERIMEN SELESAI ===")
for key, val in dict_master_results.items():
    print(f"{key} -> Best CV PR-AUC: {val['best_cv_pr_auc']:.4f}")
✅ Berhasil me-load backup CPU!
Terdapat 24 eksperimen yang sudah selesai.
Sistem akan melewati eksperimen tersebut dan melanjutkan sisanya...

Melewati ppk_keep_all_RandomForest karena sudah selesai dan tersimpan.
Melewati ppk_keep_corr_RandomForest karena sudah selesai dan tersimpan.
Melewati ppk_impute_all_RandomForest karena sudah selesai dan tersimpan.
Melewati ppk_impute_all_AdaBoost karena sudah selesai dan tersimpan.
Melewati ppk_impute_corr_RandomForest karena sudah selesai dan tersimpan.
Melewati ppk_impute_corr_AdaBoost karena sudah selesai dan tersimpan.
Melewati ppk_impute_pca_RandomForest karena sudah selesai dan tersimpan.
Melewati ppk_impute_pca_AdaBoost karena sudah selesai dan tersimpan.
Melewati negeq_keep_all_RandomForest karena sudah selesai dan tersimpan.
Melewati negeq_keep_corr_RandomForest karena sudah selesai dan tersimpan.
Melewati negeq_impute_all_RandomForest karena sudah selesai dan tersimpan.
Melewati negeq_impute_all_AdaBoost karena sudah selesai dan tersimpan.
Melewati negeq_impute_corr_RandomForest karena sudah selesai dan tersimpan.
Melewati negeq_impute_corr_AdaBoost karena sudah selesai dan tersimpan.
Melewati negeq_impute_pca_RandomForest karena sudah selesai dan tersimpan.
Melewati negeq_impute_pca_AdaBoost karena sudah selesai dan tersimpan.
Melewati conloss_keep_all_RandomForest karena sudah selesai dan tersimpan.
Melewati conloss_keep_corr_RandomForest karena sudah selesai dan tersimpan.
Melewati conloss_impute_all_RandomForest karena sudah selesai dan tersimpan.
Melewati conloss_impute_all_AdaBoost karena sudah selesai dan tersimpan.
Melewati conloss_impute_corr_RandomForest karena sudah selesai dan tersimpan.
Melewati conloss_impute_corr_AdaBoost karena sudah selesai dan tersimpan.
Melewati conloss_impute_pca_RandomForest karena sudah selesai dan tersimpan.
Melewati conloss_impute_pca_AdaBoost karena sudah selesai dan tersimpan.

=== EKSPERIMEN SELESAI ===
ppk_keep_all_RandomForest -> Best CV PR-AUC: 0.5010
ppk_keep_corr_RandomForest -> Best CV PR-AUC: 0.5161
ppk_impute_all_RandomForest -> Best CV PR-AUC: 0.3698
ppk_impute_all_AdaBoost -> Best CV PR-AUC: 0.3105
ppk_impute_corr_RandomForest -> Best CV PR-AUC: 0.3433
ppk_impute_corr_AdaBoost -> Best CV PR-AUC: 0.3210
ppk_impute_pca_RandomForest -> Best CV PR-AUC: 0.3397
ppk_impute_pca_AdaBoost -> Best CV PR-AUC: 0.3101
negeq_keep_all_RandomForest -> Best CV PR-AUC: 0.4171
negeq_keep_corr_RandomForest -> Best CV PR-AUC: 0.4463
negeq_impute_all_RandomForest -> Best CV PR-AUC: 0.1105
negeq_impute_all_AdaBoost -> Best CV PR-AUC: 0.0901
negeq_impute_corr_RandomForest -> Best CV PR-AUC: 0.1090
negeq_impute_corr_AdaBoost -> Best CV PR-AUC: 0.0845
negeq_impute_pca_RandomForest -> Best CV PR-AUC: 0.1197
negeq_impute_pca_AdaBoost -> Best CV PR-AUC: 0.0847
conloss_keep_all_RandomForest -> Best CV PR-AUC: 0.5440
conloss_keep_corr_RandomForest -> Best CV PR-AUC: 0.5423
conloss_impute_all_RandomForest -> Best CV PR-AUC: 0.2272
conloss_impute_all_AdaBoost -> Best CV PR-AUC: 0.1912
conloss_impute_corr_RandomForest -> Best CV PR-AUC: 0.2365
conloss_impute_corr_AdaBoost -> Best CV PR-AUC: 0.1855
conloss_impute_pca_RandomForest -> Best CV PR-AUC: 0.1935
conloss_impute_pca_AdaBoost -> Best CV PR-AUC: 0.1858

Asesmen Model (Find Best Threshold & Fit Predict)

Threshold Finding

# 1. Inisialisasi dictionary
if platform == "gcolab":
    dict_master_results = {} # Mulai dengan dictionary kosong di Colab
    print("Inisialisasi dict_master_results kosong di Google Colab.")
    
    for c_type in ["cpu","gpu"]:
        file_path = prefix_path + f'pickle/eksperimen_skripsi_backup_{c_type}.pkl'
        if os.path.exists(file_path):
            with open(file_path, 'rb') as f:
                data = pickle.load(f)
                dict_master_results.update(data) # Menggabungkan dictionary
                print(f"Berhasil me-load data backup {c_type.upper()} ({len(data)} eksperimen).")
        else:
            print(f"File backup {c_type.upper()} tidak ditemukan di: {file_path}")

elif platform == "kaggle": # Kaggle

    # kalau pakai cpu yang dibuka gpu karena cpu sudah ada di var
    # begitu pula sebaliknya
    if compute_type == 'cpu':
        file_path = prefix_path + f'pickle/eksperimen_skripsi_backup_gpu.pkl'
    elif compute_type == 'gpu':
        file_path = prefix_path + f'pickle/eksperimen_skripsi_backup_cpu.pkl'
    
    if os.path.exists(file_path):
        with open(file_path, 'rb') as f:
            data = pickle.load(f)
            dict_master_results.update(data) # Menggabungkan dictionary
            print(f"Berhasil me-load backup SELAIN {compute_type.upper()} dengan {len(data)} eksperimen.")
    else:
        print(f"File backup {compute_type.upper()} tidak ditemukan di: {file_path}")


print(f"Total seluruh eksperimen yang siap dicari threshold-nya: {len(dict_master_results)}")

# Mapping string nama model ke Class Modelnya
dict_model_classes = {
    'RandomForest': RandomForestClassifier,
    'XGBoost': XGBClassifier,
    'LightGBM': LGBMClassifier,
    'AdaBoost': AdaBoostClassifier,
    'CatBoost': CatBoostClassifier
}

# dict untuk menyimpan best threshold dari OOF predict
dict_best_thresholds = {}

threshold_pkl_path = save_path + 'best_thresholds_skripsi.pkl'

if resume_from_backup:
    try:
        with open(threshold_pkl_path, 'rb') as f:
            dict_best_thresholds = pickle.load(f)
        print("✅ Berhasil me-load Model dan Threshold dari backup.")
    except FileNotFoundError:
        print("⚠️ File backup model/threshold tidak ditemukan. Memaksa fit ulang...")
        resume_from_backup = False

print("\n========== MEMULAI PROSES PENCARIAN THRESHOLD OPTIMAL (OOF-CV) ==========")

# Looping ke seluruh hasil eksperimen yang ada di master
for exp_id, result_data in dict_master_results.items():

    # KONDISI RESUME: Jika threshold sudah ada di dict, skip eksperimen ini
    if resume_from_backup and (exp_id in dict_best_thresholds):
        print(f"⏩ Skip OOF-CV Threshold Finding untuk: {exp_id} (Sudah ada di backup)")
        continue


    print(f"⚙️ Mencari Threshold untuk: {exp_id}")
    
    # Parsing Konfigurasi dari exp_id (Format: target_nstrat_fstrat_model)
    parts = exp_id.split('_')
    t_name, n_strat, f_strat, m_name = parts[0], parts[1], parts[2], parts[3]
    
    # Ambil parameter terbaik & Class Model
    best_params = result_data['best_params']
    model_class = dict_model_classes[m_name]

    # Atur deterministic parameter tambahan agar hasil konsisten
    if model_class is RandomForestClassifier:
        best_params['n_jobs'] = 1
        best_params['random_state'] = 42
    elif model_class is XGBClassifier:
        best_params['n_jobs'] = 1
    elif model_class is LGBMClassifier:
        best_params['deterministic'] = True
        best_params['force_col_wise'] = True
        best_params['n_jobs'] = 1 
        best_params['verbose'] = -1
    elif model_class is CatBoostClassifier:
        best_params['thread_count'] = 1
        best_params['random_seed'] = 42
        best_params['verbose'] = False

    # Data Raw whole train
    X_train_final = dict_data_splits[t_name]['X_train'].copy()
    y_train_final = dict_data_splits[t_name]['y_train'].copy()
    # X_test_final = dict_data_splits[t_name]['X_test'].copy()
    # y_test_final = dict_data_splits[t_name]['y_test'].copy()
    
    # --- 4. REPLIKASI PREPROCESSING (SESUAI KONFIGURASI) ---
    
    # A. Missing Indicator & Imputasi
    if n_strat == 'impute':
        # Tambah missing indicator (Train dan Test)
        cols_to_flag = kolom_missing_indicator[t_name]
        for col in cols_to_flag:
            X_train_final[f"{col}_is_missing"] = X_train_final[col].isnull().astype(int)
            # X_test_final[f"{col}_is_missing"] = X_test_final[col].isnull().astype(int)
            
        # Terapkan Custom Imputation
        fitur_cols = [c for c in X_train_final.columns if c not in metadata_cols]
        X_train_final, _ = apply_custom_imputation(X_train_final, X_test_final, fitur_cols)

    # Simpan metadata untuk nanti digabung kembali jika diperlukan (misal untuk PCA yang butuh fitur asli)
    meta_tr = X_train_final[metadata_cols].reset_index(drop=True)
    # meta_ts = X_test_final[metadata_cols].reset_index(drop=True)
        
    # B. Strategi Fitur (Correlation Drop / PCA)
    if f_strat == 'corr':
        # Cari fitur korelasi tinggi HANYA di Train, drop di Train dan Test
        cols_to_drop = get_low_corr_features_target_aware(X_train_final, y_train_final, threshold=0.85)
        X_train_final = X_train_final.drop(columns=cols_to_drop)
        # X_test_final = X_test_final.drop(columns=cols_to_drop)
        
    elif f_strat == 'pca':
        fitur_cols = [c for c in X_train_final.columns if c not in metadata_cols]

        # Buang Fitur Konstan dulu Sebelum PCA (agar PCA tidak error)
        constant_cols = [col for col in fitur_cols if X_train_final[col].nunique() <= 1]
        if constant_cols:
            X_train_final = X_train_final.drop(columns=constant_cols)
            # X_test_final = X_test_final.drop(columns=constant_cols)
            fitur_cols = [c for c in fitur_cols if c not in constant_cols]

        scaler = StandardScaler()
        pca = PCA(n_components=0.95, random_state=42)
        
        # Fit Transform di Train, Transform di Test
        X_tr_pca = pca.fit_transform(scaler.fit_transform(X_train_final[fitur_cols]))
        # X_ts_pca = pca.transform(scaler.transform(X_test_final[fitur_cols]))
        
        # Rekonstruksi DataFrame (tempelkan metadata lagi)
        pca_cols = [f"PC_{i}" for i in range(X_tr_pca.shape[1])]
        X_train_final = pd.concat([meta_tr, pd.DataFrame(X_tr_pca, columns=pca_cols)], axis=1)
        # X_test_final = pd.concat([meta_ts, pd.DataFrame(X_ts_pca, columns=pca_cols)], axis=1)

    # C. Drop Metadata sebelum masuk ke model
    X_train_clean = X_train_final.drop(columns=metadata_cols, errors='ignore')
    # X_test_clean = X_test_final.drop(columns=metadata_cols, errors='ignore')




    # Ambil index custom CV dari data train (sebelum urutannya berubah)
    cv_indices_eval = get_custom_cv_indices(X_train_final)

    # Loop Manual untuk Out-of-Fold Probabilities (Mengatasi ValueError)
    # Buat array kosong diisi NaN sepanjang data train
    y_train_oof_probs = np.full(len(X_train_clean), np.nan)
    
    for train_idx, val_idx in cv_indices_eval:
        clone_model = model_class(**best_params)
        
        # Potong data sesuai indeks Custom CV
        X_tr_fold = X_train_clean.iloc[train_idx]
        y_tr_fold = y_train_final.iloc[train_idx]
        X_val_fold = X_train_clean.iloc[val_idx]
        
        # Fit dan Prediksi pada lipatan ini
        clone_model.fit(X_tr_fold, y_tr_fold)
        y_train_oof_probs[val_idx] = clone_model.predict_proba(X_val_fold)[:, 1]

    # Saring array, buang indeks yang tidak pernah masuk ke lipatan validasi (NaN)
    valid_mask = ~np.isnan(y_train_oof_probs)
    y_train_oof_probs_clean = y_train_oof_probs[valid_mask]
    
    # Gunakan to_numpy() agar tidak ada masalah pergeseran index bawaan Pandas
    y_train_true_clean = y_train_final.to_numpy()[valid_mask]

    # Cari threshold optimal MURNI DARI DATA TRAIN
    precisions_tr, recalls_tr, thresholds_tr = precision_recall_curve(y_train_true_clean, y_train_oof_probs_clean)
    # precisions_tr, recalls_tr, thresholds_tr = precision_recall_curve(y_train_final, y_train_oof_probs)
    target_recall = 0.70

    # Proses cari best threshold
    try:
        optimal_idx = np.where(recalls_tr >= target_recall)[0][-1]
        if optimal_idx >= len(thresholds_tr):
            optimal_idx = len(thresholds_tr) - 1
        best_threshold = thresholds_tr[optimal_idx]
    except IndexError:
        best_threshold = 0.5


    dict_best_thresholds[exp_id] = best_threshold
    with open(threshold_pkl_path, 'wb') as f:
        pickle.dump(dict_best_thresholds, f)


print("\n=== PROSES PENCARIAN THRESHOLD SELESAI ===")
Inisialisasi dict_master_results kosong di Google Colab.
Berhasil me-load data backup CPU (24 eksperimen).
Berhasil me-load data backup GPU (45 eksperimen).
Total seluruh eksperimen yang siap dicari threshold-nya: 69
✅ Berhasil me-load Model dan Threshold dari backup.

========== MEMULAI PROSES PENCARIAN THRESHOLD OPTIMAL (OOF-CV) ==========
⏩ Skip OOF-CV Threshold Finding untuk: ppk_keep_all_RandomForest (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: ppk_keep_corr_RandomForest (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: ppk_impute_all_RandomForest (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: ppk_impute_all_AdaBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: ppk_impute_corr_RandomForest (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: ppk_impute_corr_AdaBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: ppk_impute_pca_RandomForest (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: ppk_impute_pca_AdaBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: negeq_keep_all_RandomForest (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: negeq_keep_corr_RandomForest (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: negeq_impute_all_RandomForest (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: negeq_impute_all_AdaBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: negeq_impute_corr_RandomForest (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: negeq_impute_corr_AdaBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: negeq_impute_pca_RandomForest (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: negeq_impute_pca_AdaBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: conloss_keep_all_RandomForest (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: conloss_keep_corr_RandomForest (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: conloss_impute_all_RandomForest (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: conloss_impute_all_AdaBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: conloss_impute_corr_RandomForest (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: conloss_impute_corr_AdaBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: conloss_impute_pca_RandomForest (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: conloss_impute_pca_AdaBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: ppk_keep_all_XGBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: ppk_keep_all_LightGBM (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: ppk_keep_all_CatBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: ppk_keep_corr_XGBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: ppk_keep_corr_LightGBM (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: ppk_keep_corr_CatBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: ppk_impute_all_XGBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: ppk_impute_all_LightGBM (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: ppk_impute_all_CatBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: ppk_impute_corr_XGBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: ppk_impute_corr_LightGBM (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: ppk_impute_corr_CatBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: ppk_impute_pca_XGBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: ppk_impute_pca_LightGBM (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: ppk_impute_pca_CatBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: negeq_keep_all_XGBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: negeq_keep_all_LightGBM (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: negeq_keep_all_CatBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: negeq_keep_corr_XGBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: negeq_keep_corr_LightGBM (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: negeq_keep_corr_CatBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: negeq_impute_all_XGBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: negeq_impute_all_LightGBM (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: negeq_impute_all_CatBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: negeq_impute_corr_XGBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: negeq_impute_corr_LightGBM (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: negeq_impute_corr_CatBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: negeq_impute_pca_XGBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: negeq_impute_pca_LightGBM (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: negeq_impute_pca_CatBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: conloss_keep_all_XGBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: conloss_keep_all_LightGBM (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: conloss_keep_all_CatBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: conloss_keep_corr_XGBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: conloss_keep_corr_LightGBM (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: conloss_keep_corr_CatBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: conloss_impute_all_XGBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: conloss_impute_all_LightGBM (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: conloss_impute_all_CatBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: conloss_impute_corr_XGBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: conloss_impute_corr_LightGBM (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: conloss_impute_corr_CatBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: conloss_impute_pca_XGBoost (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: conloss_impute_pca_LightGBM (Sudah ada di backup)
⏩ Skip OOF-CV Threshold Finding untuk: conloss_impute_pca_CatBoost (Sudah ada di backup)

=== PROSES PENCARIAN THRESHOLD SELESAI ===
dict_best_thresholds
{'ppk_keep_all_RandomForest': np.float64(0.1026076616985708),
 'ppk_keep_corr_RandomForest': np.float64(0.09740742823844975),
 'ppk_impute_all_RandomForest': np.float64(0.07135664391305577),
 'ppk_impute_all_AdaBoost': np.float64(0.35292381769287745),
 'ppk_impute_corr_RandomForest': np.float64(0.06876373959258938),
 'ppk_impute_corr_AdaBoost': np.float64(0.2706962006722341),
 'ppk_impute_pca_RandomForest': np.float64(0.11050689486787096),
 'ppk_impute_pca_AdaBoost': np.float64(0.4295207244500669),
 'negeq_keep_all_RandomForest': np.float64(0.059480461812911195),
 'negeq_keep_corr_RandomForest': np.float64(0.05046650879984213),
 'negeq_impute_all_RandomForest': np.float64(0.008311750428088816),
 'negeq_impute_all_AdaBoost': np.float64(0.3452967440652882),
 'negeq_impute_corr_RandomForest': np.float64(0.008418424698410443),
 'negeq_impute_corr_AdaBoost': np.float64(0.39055291556829064),
 'negeq_impute_pca_RandomForest': np.float64(0.009023368159728705),
 'negeq_impute_pca_AdaBoost': np.float64(0.24982568084045162),
 'conloss_keep_all_RandomForest': np.float64(0.10313216101867166),
 'conloss_keep_corr_RandomForest': np.float64(0.09407666229288404),
 'conloss_impute_all_RandomForest': np.float64(0.07689151930468276),
 'conloss_impute_all_AdaBoost': np.float64(0.16221623091726856),
 'conloss_impute_corr_RandomForest': np.float64(0.07632841380226682),
 'conloss_impute_corr_AdaBoost': np.float64(0.18046469323195244),
 'conloss_impute_pca_RandomForest': np.float64(0.07417635658914729),
 'conloss_impute_pca_AdaBoost': np.float64(0.3950771133294802),
 'ppk_keep_all_XGBoost': np.float64(0.015902331098914146),
 'ppk_keep_all_LightGBM': np.float64(0.0025382668280570612),
 'ppk_keep_all_CatBoost': np.float64(0.01813688630925825),
 'ppk_keep_corr_XGBoost': np.float64(0.03040345199406147),
 'ppk_keep_corr_LightGBM': np.float64(0.008526471325700261),
 'ppk_keep_corr_CatBoost': np.float64(0.009480744680310961),
 'ppk_impute_all_XGBoost': np.float64(0.012296569533646107),
 'ppk_impute_all_LightGBM': np.float64(3.415264436267724e-05),
 'ppk_impute_all_CatBoost': np.float64(0.004135391656362598),
 'ppk_impute_corr_XGBoost': np.float64(0.07499814033508301),
 'ppk_impute_corr_LightGBM': np.float64(0.004446654234049561),
 'ppk_impute_corr_CatBoost': np.float64(0.00401453094057985),
 'ppk_impute_pca_XGBoost': np.float64(0.06325820833444595),
 'ppk_impute_pca_LightGBM': np.float64(0.001057382472860725),
 'ppk_impute_pca_CatBoost': np.float64(0.036930656688580636),
 'negeq_keep_all_XGBoost': np.float64(0.011342362500727177),
 'negeq_keep_all_LightGBM': np.float64(0.0008399380078398809),
 'negeq_keep_all_CatBoost': np.float64(0.012688657018005924),
 'negeq_keep_corr_XGBoost': np.float64(0.01108479779213667),
 'negeq_keep_corr_LightGBM': np.float64(0.00013188201136054325),
 'negeq_keep_corr_CatBoost': np.float64(0.007393605695564846),
 'negeq_impute_all_XGBoost': np.float64(0.008673484437167645),
 'negeq_impute_all_LightGBM': np.float64(2.6023672887597216e-05),
 'negeq_impute_all_CatBoost': np.float64(0.020205113489548995),
 'negeq_impute_corr_XGBoost': np.float64(0.00426494050770998),
 'negeq_impute_corr_LightGBM': np.float64(4.513603303341643e-07),
 'negeq_impute_corr_CatBoost': np.float64(0.11106818494619851),
 'negeq_impute_pca_XGBoost': np.float64(0.008357434533536434),
 'negeq_impute_pca_LightGBM': np.float64(0.007491902745421547),
 'negeq_impute_pca_CatBoost': np.float64(0.027811120171266263),
 'conloss_keep_all_XGBoost': np.float64(0.06068401038646698),
 'conloss_keep_all_LightGBM': np.float64(0.023755998304761973),
 'conloss_keep_all_CatBoost': np.float64(0.04315520980250645),
 'conloss_keep_corr_XGBoost': np.float64(0.06285607069730759),
 'conloss_keep_corr_LightGBM': np.float64(0.0026665631522638364),
 'conloss_keep_corr_CatBoost': np.float64(0.05833520459810593),
 'conloss_impute_all_XGBoost': np.float64(0.07634969055652618),
 'conloss_impute_all_LightGBM': np.float64(0.07720331008273537),
 'conloss_impute_all_CatBoost': np.float64(0.0819846305283936),
 'conloss_impute_corr_XGBoost': np.float64(0.07830634713172913),
 'conloss_impute_corr_LightGBM': np.float64(0.07801975274385842),
 'conloss_impute_corr_CatBoost': np.float64(0.08380675600146752),
 'conloss_impute_pca_XGBoost': np.float64(0.024094466120004654),
 'conloss_impute_pca_LightGBM': np.float64(0.0014064269861669264),
 'conloss_impute_pca_CatBoost': np.float64(0.07269169798521348)}

Final Test (Fit & Predict)

# Load atau inisialisasi backup model final
dict_fitted_models = {}
model_pkl_path = save_path + 'fitted_models_skripsi.pkl'

print(f"Total seluruh eksperimen yang siap dievaluasi: {len(dict_master_results)}")

# if resume_from_backup:
#     try:
#         with open(model_pkl_path, 'rb') as f:
#             dict_fitted_models = pickle.load(f)
#         print(f"✅ Berhasil me-load {len(dict_fitted_models)} Model Ter-fit dari backup.")
#     except FileNotFoundError:
#         print("⚠️ File backup model final tidak ditemukan. Akan melakukan fitting dari awal...")
#         # resume_from_backup = False

# List untuk menyimpan rekapan performa Test dari SEMUA eksperimen
final_evaluation_results = []

# Mnampung metrik kurva PR per target
pr_auc_plot_data = {'ppk': [], 'negeq': [], 'conloss': []}

# dict menampung detail prediksi tiap baris data
dict_detailed_predictions = {}



print("========== MEMULAI EVALUASI FINAL SELURUH EKSPERIMEN ==========")

# Looping ke seluruh hasil eksperimen yang ada di master
for exp_id, result_data in dict_master_results.items():
    print(f"\nMengevaluasi Hasil: {exp_id}")
    
    # Parsing Konfigurasi dari exp_id (Format: target_nstrat_fstrat_model)
    parts = exp_id.split('_')
    t_name, n_strat, f_strat, m_name = parts[0], parts[1], parts[2], parts[3]
    
    # Ambil parameter terbaik, threshold terbaik & Class Model
    best_params = result_data['best_params']
    model_class = dict_model_classes[m_name]
    best_threshold = dict_best_thresholds[exp_id]

    # Atur deterministic parameter tambahan agar hasil konsisten
    if model_class is RandomForestClassifier:
        best_params['n_jobs'] = 1
        best_params['random_state'] = 42
    elif model_class is XGBClassifier:
        best_params['n_jobs'] = 1
    elif model_class is LGBMClassifier:
        best_params['deterministic'] = True
        best_params['force_col_wise'] = True
        best_params['n_jobs'] = 1 
        best_params['verbose'] = -1
    elif model_class is CatBoostClassifier:
        best_params['thread_count'] = 1
        best_params['random_seed'] = 42
        best_params['verbose'] = False
    
    # 3. Tarik Data Raw (Train utuh dan Test masa depan)
    X_train_final = dict_data_splits[t_name]['X_train'].copy()
    y_train_final = dict_data_splits[t_name]['y_train'].copy()
    X_test_final = dict_data_splits[t_name]['X_test'].copy()
    y_test_final = dict_data_splits[t_name]['y_test'].copy()
    
    # --- 4. REPLIKASI PREPROCESSING (SESUAI KONFIGURASI) ---
    
    # A. Missing Indicator & Imputasi
    if n_strat == 'impute':
        # Tambah missing indicator (Train dan Test)
        cols_to_flag = kolom_missing_indicator[t_name]
        for col in cols_to_flag:
            X_train_final[f"{col}_is_missing"] = X_train_final[col].isnull().astype(int)
            X_test_final[f"{col}_is_missing"] = X_test_final[col].isnull().astype(int)
            
        # Terapkan Custom Imputation
        fitur_cols = [c for c in X_train_final.columns if c not in metadata_cols]
        X_train_final, X_test_final = apply_custom_imputation(X_train_final, X_test_final, fitur_cols)

    # Simpan metadata untuk nanti digabung kembali jika diperlukan (misal untuk PCA yang butuh fitur asli)
    meta_tr = X_train_final[metadata_cols].reset_index(drop=True)
    meta_ts = X_test_final[metadata_cols].reset_index(drop=True) # cek kepake di cell ini atau ga
        
    # B. Strategi Fitur (Correlation Drop / PCA)
    if f_strat == 'corr':
        # Cari fitur korelasi tinggi HANYA di Train, drop di Train dan Test
        cols_to_drop = get_low_corr_features_target_aware(X_train_final, y_train_final, threshold=0.85)
        X_train_final = X_train_final.drop(columns=cols_to_drop)
        X_test_final = X_test_final.drop(columns=cols_to_drop)
        
    elif f_strat == 'pca':
        fitur_cols = [c for c in X_train_final.columns if c not in metadata_cols]

        # Buang Fitur Konstan dulu Sebelum PCA (agar PCA tidak error)
        constant_cols = [col for col in fitur_cols if X_train_final[col].nunique() <= 1]
        if constant_cols:
            X_train_final = X_train_final.drop(columns=constant_cols)
            X_test_final = X_test_final.drop(columns=constant_cols)
            fitur_cols = [c for c in fitur_cols if c not in constant_cols]

        scaler = StandardScaler()
        pca = PCA(n_components=0.95, random_state=42)
        
        # Fit Transform di Train, Transform di Test
        X_tr_pca = pca.fit_transform(scaler.fit_transform(X_train_final[fitur_cols]))
        X_ts_pca = pca.transform(scaler.transform(X_test_final[fitur_cols]))
        
        # Rekonstruksi DataFrame (tempelkan metadata lagi)
        pca_cols = [f"PC_{i}" for i in range(X_tr_pca.shape[1])]
        X_train_final = pd.concat([meta_tr, pd.DataFrame(X_tr_pca, columns=pca_cols)], axis=1)
        X_test_final = pd.concat([meta_ts, pd.DataFrame(X_ts_pca, columns=pca_cols)], axis=1)

    # C. Drop Metadata sebelum masuk ke model
    X_train_clean = X_train_final.drop(columns=metadata_cols, errors='ignore')
    X_test_clean = X_test_final.drop(columns=metadata_cols, errors='ignore')




    print(f"⚙️ Melakukan Fitting Model Final ke Whole Train Set...")

    # Latih model final sesungguhnya ke SELURUH data Train
    model = model_class(**best_params)
    model.fit(X_train_clean, y_train_final)

    # simpan model dan threshold
    dict_fitted_models[exp_id] = model

    with open(model_pkl_path, 'wb') as f:
        pickle.dump(dict_fitted_models, f)


    # Prediksi probabilitas pada Test Set
    y_pred_probs = model.predict_proba(X_test_clean)[:, 1]

    # Hitung Test PR-AUC
    test_pr_auc = average_precision_score(y_test_final, y_pred_probs)
    
    # Terapkan Threshold yang sudah didapat sebelumnya
    y_pred_final = (y_pred_probs >= best_threshold).astype(int)

    # Hitung Performa Real di Dunia Nyata
    achieved_recall = recall_score(y_test_final, y_pred_final)
    expected_precision = precision_score(y_test_final, y_pred_final, zero_division=0)

    cm = confusion_matrix(y_test_final, y_pred_final)
    tn, fp, fn, tp = cm.ravel()

    # [METRIK TAMBAHAN BARU]
    test_f1 = f1_score(y_test_final, y_pred_final, zero_division=0)
    test_accuracy = accuracy_score(y_test_final, y_pred_final)
    test_specificity = tn / (tn + fp) if (tn + fp) > 0 else 0.0

    # Simpan detail prediksi dan metadata ke dalam keranjang
    dict_detailed_predictions[exp_id] = {
        'metadata': meta_ts.copy(),
        'y_true': y_test_final.reset_index(drop=True),
        'y_pred': pd.Series(y_pred_final),
        'y_prob': pd.Series(y_pred_probs),
        'X_test_features': X_test_clean.reset_index(drop=True) # simpan fitur
    }
    
    # --- 7. SIMPAN HASIL KE REKAPAN ---
    final_evaluation_results.append({
        'Experiment_ID': exp_id,
        'Target': t_name,
        'Null_Strat': n_strat,
        'Feat_Strat': f_strat,
        'Model': m_name,
        'Optuna_PR_AUC': result_data['best_cv_pr_auc'], # Dari Optuna
        'Test_PR_AUC': test_pr_auc,                 # Di Test Set riil
        'Best_Threshold': best_threshold,
        'Test_Recall': achieved_recall,
        'Test_Precision': expected_precision,
        'Confusion_Matrix': cm.tolist(),
        'Test_Specificity': test_specificity, # Masuk ke tabel
        'Test_F1': test_f1,                   # Masuk ke tabel
        'Test_Accuracy': test_accuracy,       # Masuk ke tabel
    })
    
    # Simpan kurva Test Set murni untuk plotting (cuma untuk visualisasi kurva, bukan untuk cari threshold)
    precisions_test, recalls_test, _ = precision_recall_curve(y_test_final, y_pred_probs)

    # Simpan metrik kurva untuk diplot di luar loop
    if t_name in pr_auc_plot_data:
        pr_auc_plot_data[t_name].append({
            'label': f"{n_strat}_{f_strat}_{m_name} (AUC={test_pr_auc:.3f})",
            'recalls': recalls_test,
            'precisions': precisions_test,
            'auc': test_pr_auc
        })



# Jadikan DataFrame agar mudah diurutkan dan dianalisis
df_final_summary = pd.DataFrame(final_evaluation_results)
df_final_summary = df_final_summary.sort_values(by=['Target', 'Test_PR_AUC'], ascending=[True, False]).reset_index(drop=True)

# print("\n========== REKAPAN PERINGKAT MODEL TERBAIK TIAP TARGET ==========")
# display(df_final_summary.head(3))

# # Hanya overwrite/simpan ulang jika kita sedang tidak menggunakan backup (artinya ada eksperimen baru yang di-fit)
# if not resume_from_backup:
#     with open(model_pkl_path, 'wb') as f:
#         pickle.dump(dict_fitted_models, f)
        
#     with open(threshold_pkl_path, 'wb') as f:
#         pickle.dump(dict_best_thresholds, f)

# Simpan Dataframe Evaluasi Final ke CSV di GDrive
csv_path = save_path + 'hasil_evaluasi_final_skripsi.csv'
df_final_summary.to_csv(csv_path, index=False)

print(f"\n✅ Evaluasi Selesai. Hasil evaluasi final diekspor ke: {csv_path}")
Total seluruh eksperimen yang siap dievaluasi: 69
========== MEMULAI EVALUASI FINAL SELURUH EKSPERIMEN ==========

Mengevaluasi Hasil: ppk_keep_all_RandomForest
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: ppk_keep_corr_RandomForest
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: ppk_impute_all_RandomForest
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: ppk_impute_all_AdaBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: ppk_impute_corr_RandomForest
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: ppk_impute_corr_AdaBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: ppk_impute_pca_RandomForest
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: ppk_impute_pca_AdaBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: negeq_keep_all_RandomForest
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: negeq_keep_corr_RandomForest
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: negeq_impute_all_RandomForest
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: negeq_impute_all_AdaBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: negeq_impute_corr_RandomForest
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: negeq_impute_corr_AdaBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: negeq_impute_pca_RandomForest
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: negeq_impute_pca_AdaBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: conloss_keep_all_RandomForest
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: conloss_keep_corr_RandomForest
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: conloss_impute_all_RandomForest
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: conloss_impute_all_AdaBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: conloss_impute_corr_RandomForest
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: conloss_impute_corr_AdaBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: conloss_impute_pca_RandomForest
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: conloss_impute_pca_AdaBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: ppk_keep_all_XGBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: ppk_keep_all_LightGBM
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: ppk_keep_all_CatBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: ppk_keep_corr_XGBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: ppk_keep_corr_LightGBM
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: ppk_keep_corr_CatBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: ppk_impute_all_XGBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: ppk_impute_all_LightGBM
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: ppk_impute_all_CatBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: ppk_impute_corr_XGBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: ppk_impute_corr_LightGBM
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: ppk_impute_corr_CatBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: ppk_impute_pca_XGBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: ppk_impute_pca_LightGBM
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: ppk_impute_pca_CatBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: negeq_keep_all_XGBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: negeq_keep_all_LightGBM
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: negeq_keep_all_CatBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: negeq_keep_corr_XGBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: negeq_keep_corr_LightGBM
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: negeq_keep_corr_CatBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: negeq_impute_all_XGBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: negeq_impute_all_LightGBM
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: negeq_impute_all_CatBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: negeq_impute_corr_XGBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: negeq_impute_corr_LightGBM
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: negeq_impute_corr_CatBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: negeq_impute_pca_XGBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: negeq_impute_pca_LightGBM
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: negeq_impute_pca_CatBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: conloss_keep_all_XGBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: conloss_keep_all_LightGBM
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: conloss_keep_all_CatBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: conloss_keep_corr_XGBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: conloss_keep_corr_LightGBM
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: conloss_keep_corr_CatBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: conloss_impute_all_XGBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: conloss_impute_all_LightGBM
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: conloss_impute_all_CatBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: conloss_impute_corr_XGBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: conloss_impute_corr_LightGBM
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: conloss_impute_corr_CatBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: conloss_impute_pca_XGBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: conloss_impute_pca_LightGBM
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

Mengevaluasi Hasil: conloss_impute_pca_CatBoost
⚙️ Melakukan Fitting Model Final ke Whole Train Set...

✅ Evaluasi Selesai. Hasil evaluasi final diekspor ke: /content/drive/MyDrive/SKRIPSI/Dataset/hasil_evaluasi_final_skripsi.csv
# resume_fit_from_backup = True

# # 1. Inisialisasi dictionary
# if platform == "gcolab":
#     dict_master_results = {} # Mulai dengan dictionary kosong di Colab
#     print("Inisialisasi dict_master_results kosong di Google Colab.")
    
#     for c_type in ["cpu","gpu"]:
#         file_path = prefix_path + f'pickle/eksperimen_skripsi_backup_{c_type}.pkl'
#         if os.path.exists(file_path):
#             with open(file_path, 'rb') as f:
#                 data = pickle.load(f)
#                 dict_master_results.update(data) # Menggabungkan dictionary
#                 print(f"Berhasil me-load data backup {c_type.upper()} ({len(data)} eksperimen).")
#         else:
#             print(f"File backup {c_type.upper()} tidak ditemukan di: {file_path}")

# elif platform == "kaggle": # Kaggle

#     # kalau pakai cpu yang dibuka gpu karena cpu sudah ada di var
#     # begitu pula sebaliknya
#     if compute_type == 'cpu':
#         file_path = prefix_path + f'pickle/eksperimen_skripsi_backup_gpu.pkl'
#     elif compute_type == 'gpu':
#         file_path = prefix_path + f'pickle/eksperimen_skripsi_backup_cpu.pkl'
    
#     if os.path.exists(file_path):
#         with open(file_path, 'rb') as f:
#             data = pickle.load(f)
#             dict_master_results.update(data) # Menggabungkan dictionary
#             print(f"Berhasil me-load backup SELAIN {compute_type.upper()} dengan {len(data)} eksperimen.")
#     else:
#         print(f"File backup {compute_type.upper()} tidak ditemukan di: {file_path}")


# print(f"Total seluruh eksperimen yang siap dievaluasi: {len(dict_master_results)}")

# # Mapping string nama model ke Class Modelnya
# dict_model_classes = {
#     'RandomForest': RandomForestClassifier,
#     'XGBoost': XGBClassifier,
#     'LightGBM': LGBMClassifier,
#     'AdaBoost': AdaBoostClassifier,
#     'CatBoost': CatBoostClassifier
# }

# # List untuk menyimpan rekapan performa Test dari SEMUA eksperimen
# final_evaluation_results = []

# # Mnampung metrik kurva PR per target
# pr_auc_plot_data = {'ppk': [], 'negeq': [], 'conloss': []}

# # dict menampung detail prediksi tiap baris data
# dict_detailed_predictions = {}

# # dict untuk menyimpan model yang udah difit
# dict_fitted_models = {}

# # dict untuk menyimpan best threshold dari OOF predict
# dict_best_thresholds = {}

# model_pkl_path = save_path + 'fitted_models_skripsi.pkl'
# threshold_pkl_path = save_path + 'best_thresholds_skripsi.pkl'

# if resume_from_backup:
#     try:
#         with open(model_pkl_path, 'rb') as f:
#             dict_fitted_models = pickle.load(f)
#         with open(threshold_pkl_path, 'rb') as f:
#             dict_best_thresholds = pickle.load(f)
#         print("✅ Berhasil me-load Model dan Threshold dari backup.")
#     except FileNotFoundError:
#         print("⚠️ File backup model/threshold tidak ditemukan. Memaksa fit ulang...")
#         resume_from_backup = False


# print("========== MEMULAI EVALUASI FINAL SELURUH EKSPERIMEN ==========")

# # Looping ke seluruh hasil eksperimen yang ada di master
# for exp_id, result_data in dict_master_results.items():
#     print(f"\nMengevaluasi: {exp_id}")
    
#     # 1. Parsing Konfigurasi dari exp_id (Format: target_nstrat_fstrat_model)
#     parts = exp_id.split('_')
#     t_name, n_strat, f_strat, m_name = parts[0], parts[1], parts[2], parts[3]
    
#     # 2. Ambil parameter terbaik & Class Model
#     best_params = result_data['best_params']
#     model_class = dict_model_classes[m_name]

#     # Atur deterministic parameter tambahan agar hasil konsisten
#     if model_class is RandomForestClassifier:
#         best_params['n_jobs'] = 1
#         best_params['random_state'] = 42
#     elif model_class is XGBClassifier:
#         best_params['n_jobs'] = 1
#     elif model_class is LGBMClassifier:
#         best_params['deterministic'] = True
#         best_params['force_col_wise'] = True
#         best_params['n_jobs'] = 1 
#     elif model_class is CatBoostClassifier:
#         best_params['thread_count'] = 1
#         best_params['random_seed'] = 42
    
#     # 3. Tarik Data Raw (Train utuh dan Test masa depan)
#     X_train_final = dict_data_splits[t_name]['X_train'].copy()
#     y_train_final = dict_data_splits[t_name]['y_train'].copy()
#     X_test_final = dict_data_splits[t_name]['X_test'].copy()
#     y_test_final = dict_data_splits[t_name]['y_test'].copy()
    
#     # --- 4. REPLIKASI PREPROCESSING (SESUAI KONFIGURASI) ---
    
#     # A. Missing Indicator & Imputasi
#     if n_strat == 'impute':
#         # Tambah missing indicator (Train dan Test)
#         cols_to_flag = kolom_missing_indicator[t_name]
#         for col in cols_to_flag:
#             X_train_final[f"{col}_is_missing"] = X_train_final[col].isnull().astype(int)
#             X_test_final[f"{col}_is_missing"] = X_test_final[col].isnull().astype(int)
            
#         # Terapkan Custom Imputation
#         fitur_cols = [c for c in X_train_final.columns if c not in metadata_cols]
#         X_train_final, X_test_final = apply_custom_imputation(X_train_final, X_test_final, fitur_cols)

#     # Simpan metadata untuk nanti digabung kembali jika diperlukan (misal untuk PCA yang butuh fitur asli)
#     meta_tr = X_train_final[metadata_cols].reset_index(drop=True)
#     meta_ts = X_test_final[metadata_cols].reset_index(drop=True)
        
#     # B. Strategi Fitur (Correlation Drop / PCA)
#     if f_strat == 'corr':
#         # Cari fitur korelasi tinggi HANYA di Train, drop di Train dan Test
#         cols_to_drop = get_low_corr_features_target_aware(X_train_final, y_train_final, threshold=0.85)
#         X_train_final = X_train_final.drop(columns=cols_to_drop)
#         X_test_final = X_test_final.drop(columns=cols_to_drop)
        
#     elif f_strat == 'pca':
#         fitur_cols = [c for c in X_train_final.columns if c not in metadata_cols]

#         # Buang Fitur Konstan dulu Sebelum PCA (agar PCA tidak error)
#         constant_cols = [col for col in fitur_cols if X_train_final[col].nunique() <= 1]
#         if constant_cols:
#             X_train_final = X_train_final.drop(columns=constant_cols)
#             X_test_final = X_test_final.drop(columns=constant_cols)
#             fitur_cols = [c for c in fitur_cols if c not in constant_cols]

#         scaler = StandardScaler()
#         pca = PCA(n_components=0.95, random_state=42)
        
#         # Fit Transform di Train, Transform di Test
#         X_tr_pca = pca.fit_transform(scaler.fit_transform(X_train_final[fitur_cols]))
#         X_ts_pca = pca.transform(scaler.transform(X_test_final[fitur_cols]))
        
#         # Rekonstruksi DataFrame (tempelkan metadata lagi)
#         pca_cols = [f"PC_{i}" for i in range(X_tr_pca.shape[1])]
#         X_train_final = pd.concat([meta_tr, pd.DataFrame(X_tr_pca, columns=pca_cols)], axis=1)
#         X_test_final = pd.concat([meta_ts, pd.DataFrame(X_ts_pca, columns=pca_cols)], axis=1)

#     # C. Drop Metadata sebelum masuk ke model
#     X_train_clean = X_train_final.drop(columns=metadata_cols, errors='ignore')
#     X_test_clean = X_test_final.drop(columns=metadata_cols, errors='ignore')



#     # =======================================================
#     # 2. BLOK BYPASS (CEK APAKAH PERLU FIT ULANG ATAU TIDAK)
#     # =======================================================
#     if resume_from_backup and (exp_id in dict_fitted_models) and (exp_id in dict_best_thresholds):
#         print(f"⏩ Bypass CV & Fit. Menggunakan model dan threshold dari backup.")
        
#         # Langsung tarik dari dictionary hasil load pickle
#         model = dict_fitted_models[exp_id]
#         best_threshold = dict_best_thresholds[exp_id]
        
#     else:
#         print(f"   ⚙️ Melakukan Cross-Validation & Fitting Model...")

#         # Ambil index custom CV dari data train (sebelum urutannya berubah)
#         cv_indices_eval = get_custom_cv_indices(X_train_final)

#         # Loop Manual untuk Out-of-Fold Probabilities (Mengatasi ValueError)
#         # Buat array kosong diisi NaN sepanjang data train
#         y_train_oof_probs = np.full(len(X_train_clean), np.nan)
        
#         for train_idx, val_idx in cv_indices_eval:
#             clone_model = model_class(**best_params)
            
#             # Potong data sesuai indeks Custom CV
#             X_tr_fold = X_train_clean.iloc[train_idx]
#             y_tr_fold = y_train_final.iloc[train_idx]
#             X_val_fold = X_train_clean.iloc[val_idx]
            
#             # Fit dan Prediksi pada lipatan ini
#             clone_model.fit(X_tr_fold, y_tr_fold)
#             y_train_oof_probs[val_idx] = clone_model.predict_proba(X_val_fold)[:, 1]

#         # Saring array, buang indeks yang tidak pernah masuk ke lipatan validasi (NaN)
#         valid_mask = ~np.isnan(y_train_oof_probs)
#         y_train_oof_probs_clean = y_train_oof_probs[valid_mask]
        
#         # Gunakan to_numpy() agar tidak ada masalah pergeseran index bawaan Pandas
#         y_train_true_clean = y_train_final.to_numpy()[valid_mask]

#         # Cari threshold optimal MURNI DARI DATA TRAIN
#         precisions_tr, recalls_tr, thresholds_tr = precision_recall_curve(y_train_true_clean, y_train_oof_probs_clean)
#         # precisions_tr, recalls_tr, thresholds_tr = precision_recall_curve(y_train_final, y_train_oof_probs)
#         target_recall = 0.70

#         # Proses cari best threshold
#         try:
#             optimal_idx = np.where(recalls_tr >= target_recall)[0][-1]
#             if optimal_idx >= len(thresholds_tr):
#                 optimal_idx = len(thresholds_tr) - 1
#             best_threshold = thresholds_tr[optimal_idx]
#         except IndexError:
#             best_threshold = 0.5

#         # Latih model final sesungguhnya ke SELURUH data Train
#         model = model_class(**best_params)
#         model.fit(X_train_clean, y_train_final)

#         # simpan model dan threshold
#         dict_fitted_models[exp_id] = model
#         dict_best_thresholds[exp_id] = best_threshold

#         with open(model_pkl_path, 'wb') as f:
#             pickle.dump(dict_fitted_models, f)
#         with open(threshold_pkl_path, 'wb') as f:
#             pickle.dump(dict_best_thresholds, f)




#         # Prediksi probabilitas pada Test Set
#         y_pred_probs = model.predict_proba(X_test_clean)[:, 1]

#         # Hitung Test PR-AUC
#         test_pr_auc = average_precision_score(y_test_final, y_pred_probs)
        
#         # Terapkan Threshold yang sudah didapat sebelumnya
#         y_pred_final = (y_pred_probs >= best_threshold).astype(int)

#         # Hitung Performa Real di Dunia Nyata
#         achieved_recall = recall_score(y_test_final, y_pred_final)
#         expected_precision = precision_score(y_test_final, y_pred_final, zero_division=0)

#         cm = confusion_matrix(y_test_final, y_pred_final)
#         tn, fp, fn, tp = cm.ravel()

#         # [METRIK TAMBAHAN BARU]
#         test_f1 = f1_score(y_test_final, y_pred_final, zero_division=0)
#         test_accuracy = accuracy_score(y_test_final, y_pred_final)
#         test_specificity = tn / (tn + fp) if (tn + fp) > 0 else 0.0

#         # Simpan detail prediksi dan metadata ke dalam keranjang
#         dict_detailed_predictions[exp_id] = {
#             'metadata': meta_ts.copy(),
#             'y_true': y_test_final.reset_index(drop=True),
#             'y_pred': pd.Series(y_pred_final),
#             'y_prob': pd.Series(y_pred_probs),
#             'X_test_features': X_test_clean.reset_index(drop=True) # simpan fitur
#         }
        
#         # --- 7. SIMPAN HASIL KE REKAPAN ---
#         final_evaluation_results.append({
#             'Experiment_ID': exp_id,
#             'Target': t_name,
#             'Null_Strat': n_strat,
#             'Feat_Strat': f_strat,
#             'Model': m_name,
#             'Optuna_PR_AUC': result_data['best_cv_pr_auc'], # Dari Optuna
#             'Test_PR_AUC': test_pr_auc,                 # Di Test Set riil
#             'Best_Threshold': best_threshold,
#             'Test_Recall': achieved_recall,
#             'Test_Precision': expected_precision,
#             'Confusion_Matrix': cm.tolist(),
#             'Test_Specificity': test_specificity, # Masuk ke tabel
#             'Test_F1': test_f1,                   # Masuk ke tabel
#             'Test_Accuracy': test_accuracy,       # Masuk ke tabel
#         })
        
#         # Simpan kurva Test Set murni untuk plotting (cuma untuk visualisasi kurva, bukan untuk cari threshold)
#         precisions_test, recalls_test, _ = precision_recall_curve(y_test_final, y_pred_probs)

#         # Simpan metrik kurva untuk diplot di luar loop
#         if t_name in pr_auc_plot_data:
#             pr_auc_plot_data[t_name].append({
#                 'label': f"{n_strat}_{f_strat}_{m_name} (AUC={test_pr_auc:.3f})",
#                 'recalls': recalls_test,
#                 'precisions': precisions_test,
#                 'auc': test_pr_auc
#             })






# # Jadikan DataFrame agar mudah diurutkan dan dianalisis
# df_final_summary = pd.DataFrame(final_evaluation_results)
# df_final_summary = df_final_summary.sort_values(by=['Target', 'Test_Precision'], ascending=[True, False]).reset_index(drop=True)

# print("\n========== REKAPAN PERINGKAT MODEL TERBAIK TIAP TARGET ==========")
# display(df_final_summary.head(3))

# # # Hanya overwrite/simpan ulang jika kita sedang tidak menggunakan backup (artinya ada eksperimen baru yang di-fit)
# # if not resume_from_backup:
# #     with open(model_pkl_path, 'wb') as f:
# #         pickle.dump(dict_fitted_models, f)
        
# #     with open(threshold_pkl_path, 'wb') as f:
# #         pickle.dump(dict_best_thresholds, f)

# # Simpan Dataframe Evaluasi Final ke CSV di GDrive
# csv_path = save_path + 'hasil_evaluasi_final_skripsi.csv'
# df_final_summary.to_csv(csv_path, index=False)

# print(f"\n✅ Proses Selesai. Hasil evaluasi final diekspor ke: {csv_path}")
# if not resume_from_backup:
#     print(f"✅ Model & Threshold disimpan di: {model_pkl_path} dan {threshold_pkl_path}")
# # 1. Inisialisasi dictionary
# if platform == "gcolab":
#     dict_master_results = {} # Mulai dengan dictionary kosong di Colab
#     print("Inisialisasi dict_master_results kosong di Google Colab.")
    
#     for c_type in ["cpu","gpu"]:
#         file_path = prefix_path + f'pickle/eksperimen_skripsi_backup_{c_type}.pkl'
#         if os.path.exists(file_path):
#             with open(file_path, 'rb') as f:
#                 data = pickle.load(f)
#                 dict_master_results.update(data) # Menggabungkan dictionary
#                 print(f"Berhasil me-load data backup {c_type.upper()} ({len(data)} eksperimen).")
#         else:
#             print(f"File backup {c_type.upper()} tidak ditemukan di: {file_path}")

# elif platform == "kaggle": # Kaggle

#     # kalau pakai cpu yang dibuka gpu karena cpu sudah ada di var
#     # begitu pula sebaliknya
#     if compute_type == 'cpu':
#         file_path = prefix_path + f'pickle/eksperimen_skripsi_backup_gpu.pkl'
#     elif compute_type == 'gpu':
#         file_path = prefix_path + f'pickle/eksperimen_skripsi_backup_cpu.pkl'
    
#     if os.path.exists(file_path):
#         with open(file_path, 'rb') as f:
#             data = pickle.load(f)
#             dict_master_results.update(data) # Menggabungkan dictionary
#             print(f"Berhasil me-load backup SELAIN {compute_type.upper()} dengan {len(data)} eksperimen.")
#     else:
#         print(f"File backup {compute_type.upper()} tidak ditemukan di: {file_path}")


# print(f"Total seluruh eksperimen yang siap dievaluasi: {len(dict_master_results)}")

# # Mapping string nama model ke Class Modelnya
# dict_model_classes = {
#     'RandomForest': RandomForestClassifier,
#     'XGBoost': XGBClassifier,
#     'LightGBM': LGBMClassifier,
#     'AdaBoost': AdaBoostClassifier,
#     'CatBoost': CatBoostClassifier
# }

# # List untuk menyimpan rekapan performa Test dari SEMUA eksperimen
# final_evaluation_results = []

# # Mnampung metrik kurva PR per target
# pr_auc_plot_data = {'ppk': [], 'negeq': [], 'conloss': []}

# # dict menampung detail prediksi tiap baris data
# dict_detailed_predictions = {}

# # dict untuk menyimpan model yang udah difit
# dict_fitted_models = {}

# print("========== MEMULAI EVALUASI FINAL SELURUH EKSPERIMEN ==========")

# # Looping ke seluruh hasil eksperimen yang ada di master
# for exp_id, result_data in dict_master_results.items():
#     print(f"\nMengevaluasi: {exp_id}")
    
#     # 1. Parsing Konfigurasi dari exp_id (Format: target_nstrat_fstrat_model)
#     parts = exp_id.split('_')
#     t_name, n_strat, f_strat, m_name = parts[0], parts[1], parts[2], parts[3]
    
#     # 2. Ambil parameter terbaik & Class Model
#     best_params = result_data['best_params']
#     model_class = dict_model_classes[m_name]

#     # Atur deterministic parameter tambahan agar hasil konsisten
#     if model_class is RandomForestClassifier:
#         best_params['n_jobs'] = 1
#         best_params['random_state'] = 42
#     elif model_class is XGBClassifier:
#         best_params['n_jobs'] = 1
#     elif model_class is LGBMClassifier:
#         best_params['deterministic'] = True
#         best_params['force_col_wise'] = True
#         best_params['n_jobs'] = 1 
#     elif model_class is CatBoostClassifier:
#         best_params['thread_count'] = 1
#         best_params['random_seed'] = 42
    
#     # 3. Tarik Data Raw (Train utuh dan Test masa depan)
#     X_train_final = dict_data_splits[t_name]['X_train'].copy()
#     y_train_final = dict_data_splits[t_name]['y_train'].copy()
#     X_test_final = dict_data_splits[t_name]['X_test'].copy()
#     y_test_final = dict_data_splits[t_name]['y_test'].copy()
    
#     # --- 4. REPLIKASI PREPROCESSING (SESUAI KONFIGURASI) ---
    
#     # A. Missing Indicator & Imputasi
#     if n_strat == 'impute':
#         # Tambah missing indicator (Train dan Test)
#         cols_to_flag = kolom_missing_indicator[t_name]
#         for col in cols_to_flag:
#             X_train_final[f"{col}_is_missing"] = X_train_final[col].isnull().astype(int)
#             X_test_final[f"{col}_is_missing"] = X_test_final[col].isnull().astype(int)
            
#         # Terapkan Custom Imputation
#         fitur_cols = [c for c in X_train_final.columns if c not in metadata_cols]
#         X_train_final, X_test_final = apply_custom_imputation(X_train_final, X_test_final, fitur_cols)

#     # Simpan metadata untuk nanti digabung kembali jika diperlukan (misal untuk PCA yang butuh fitur asli)
#     meta_tr = X_train_final[metadata_cols].reset_index(drop=True)
#     meta_ts = X_test_final[metadata_cols].reset_index(drop=True)
        
#     # B. Strategi Fitur (Correlation Drop / PCA)
#     if f_strat == 'corr':
#         # Cari fitur korelasi tinggi HANYA di Train, drop di Train dan Test
#         cols_to_drop = get_low_corr_features_target_aware(X_train_final, y_train_final, threshold=0.85)
#         X_train_final = X_train_final.drop(columns=cols_to_drop)
#         X_test_final = X_test_final.drop(columns=cols_to_drop)
        
#     elif f_strat == 'pca':
#         fitur_cols = [c for c in X_train_final.columns if c not in metadata_cols]

#         # Buang Fitur Konstan dulu Sebelum PCA (agar PCA tidak error)
#         constant_cols = [col for col in fitur_cols if X_train_final[col].nunique() <= 1]
#         if constant_cols:
#             X_train_final = X_train_final.drop(columns=constant_cols)
#             X_test_final = X_test_final.drop(columns=constant_cols)
#             fitur_cols = [c for c in fitur_cols if c not in constant_cols]

#         scaler = StandardScaler()
#         pca = PCA(n_components=0.95, random_state=42)
        
#         # Fit Transform di Train, Transform di Test
#         X_tr_pca = pca.fit_transform(scaler.fit_transform(X_train_final[fitur_cols]))
#         X_ts_pca = pca.transform(scaler.transform(X_test_final[fitur_cols]))
        
#         # Rekonstruksi DataFrame (tempelkan metadata lagi)
#         pca_cols = [f"PC_{i}" for i in range(X_tr_pca.shape[1])]
#         X_train_final = pd.concat([meta_tr, pd.DataFrame(X_tr_pca, columns=pca_cols)], axis=1)
#         X_test_final = pd.concat([meta_ts, pd.DataFrame(X_ts_pca, columns=pca_cols)], axis=1)

#     # C. Drop Metadata sebelum masuk ke model
#     X_train_clean = X_train_final.drop(columns=metadata_cols, errors='ignore')
#     X_test_clean = X_test_final.drop(columns=metadata_cols, errors='ignore')



#     # Ambil index custom CV dari data train (sebelum urutannya berubah)
#     cv_indices_eval = get_custom_cv_indices(X_train_final)

#     # Loop Manual untuk Out-of-Fold Probabilities (Mengatasi ValueError)
#     # Buat array kosong diisi NaN sepanjang data train
#     y_train_oof_probs = np.full(len(X_train_clean), np.nan)
    
#     for train_idx, val_idx in cv_indices_eval:
#         clone_model = model_class(**best_params)
        
#         # Potong data sesuai indeks Custom CV
#         X_tr_fold = X_train_clean.iloc[train_idx]
#         y_tr_fold = y_train_final.iloc[train_idx]
#         X_val_fold = X_train_clean.iloc[val_idx]
        
#         # Fit dan Prediksi pada lipatan ini
#         clone_model.fit(X_tr_fold, y_tr_fold)
#         y_train_oof_probs[val_idx] = clone_model.predict_proba(X_val_fold)[:, 1]

#     # Saring array, buang indeks yang tidak pernah masuk ke lipatan validasi (NaN)
#     valid_mask = ~np.isnan(y_train_oof_probs)
#     y_train_oof_probs_clean = y_train_oof_probs[valid_mask]
    
#     # Gunakan to_numpy() agar tidak ada masalah pergeseran index bawaan Pandas
#     y_train_true_clean = y_train_final.to_numpy()[valid_mask]

#     # Cari threshold optimal MURNI DARI DATA TRAIN
#     precisions_tr, recalls_tr, thresholds_tr = precision_recall_curve(y_train_true_clean, y_train_oof_probs_clean)
#     # precisions_tr, recalls_tr, thresholds_tr = precision_recall_curve(y_train_final, y_train_oof_probs)
#     target_recall = 0.70

#     # Proses cari best threshold
#     try:
#         optimal_idx = np.where(recalls_tr >= target_recall)[0][-1]
#         if optimal_idx >= len(thresholds_tr):
#             optimal_idx = len(thresholds_tr) - 1
#         best_threshold = thresholds_tr[optimal_idx]
#     except IndexError:
#         best_threshold = 0.5




#     # Latih model final sesungguhnya ke SELURUH data Train
#     model = model_class(**best_params)
#     model.fit(X_train_clean, y_train_final)

#     # simpan model
#     dict_fitted_models[exp_id] = model


#     # Prediksi probabilitas pada Test Set
#     y_pred_probs = model.predict_proba(X_test_clean)[:, 1]

#     # Hitung Test PR-AUC
#     test_pr_auc = average_precision_score(y_test_final, y_pred_probs)
    
#     # Terapkan Threshold yang sudah didapat sebelumnya
#     y_pred_final = (y_pred_probs >= best_threshold).astype(int)

#     # Hitung Performa Real di Dunia Nyata
#     achieved_recall = recall_score(y_test_final, y_pred_final)
#     expected_precision = precision_score(y_test_final, y_pred_final, zero_division=0)

#     cm = confusion_matrix(y_test_final, y_pred_final)
#     tn, fp, fn, tp = cm.ravel()

#     # [METRIK TAMBAHAN BARU]
#     test_f1 = f1_score(y_test_final, y_pred_final, zero_division=0)
#     test_accuracy = accuracy_score(y_test_final, y_pred_final)
#     test_specificity = tn / (tn + fp) if (tn + fp) > 0 else 0.0

#     # Simpan detail prediksi dan metadata ke dalam keranjang
#     dict_detailed_predictions[exp_id] = {
#         'metadata': meta_ts.copy(),
#         'y_true': y_test_final.reset_index(drop=True),
#         'y_pred': pd.Series(y_pred_final),
#         'y_prob': pd.Series(y_pred_probs),
#         'X_test_features': X_test_clean.reset_index(drop=True) # simpan fitur
#     }
    
#     # --- 7. SIMPAN HASIL KE REKAPAN ---
#     final_evaluation_results.append({
#         'Experiment_ID': exp_id,
#         'Target': t_name,
#         'Null_Strat': n_strat,
#         'Feat_Strat': f_strat,
#         'Model': m_name,
#         'Optuna_PR_AUC': result_data['best_cv_pr_auc'], # Dari Optuna
#         'Test_PR_AUC': test_pr_auc,                 # Di Test Set riil
#         'Best_Threshold': best_threshold,
#         'Test_Recall': achieved_recall,
#         'Test_Precision': expected_precision,
#         'Confusion_Matrix': cm.tolist(),
#         'Test_Specificity': test_specificity, # Masuk ke tabel
#         'Test_F1': test_f1,                   # Masuk ke tabel
#         'Test_Accuracy': test_accuracy,       # Masuk ke tabel
#     })
    
#     # Simpan kurva Test Set murni untuk plotting (cuma untuk visualisasi kurva, bukan untuk cari threshold)
#     precisions_test, recalls_test, _ = precision_recall_curve(y_test_final, y_pred_probs)

#     # Simpan metrik kurva untuk diplot di luar loop
#     if t_name in pr_auc_plot_data:
#         pr_auc_plot_data[t_name].append({
#             'label': f"{n_strat}_{f_strat}_{m_name} (AUC={test_pr_auc:.3f})",
#             'recalls': recalls_test,
#             'precisions': precisions_test,
#             'auc': test_pr_auc
#         })

# # Jadikan DataFrame agar mudah diurutkan dan dianalisis
# df_final_summary = pd.DataFrame(final_evaluation_results)
# df_final_summary = df_final_summary.sort_values(by=['Target', 'Test_Precision'], ascending=[True, False]).reset_index(drop=True)

# print("\n========== REKAPAN PERINGKAT MODEL TERBAIK TIAP TARGET ==========")
# display(df_final_summary.head(3))

# # SIMPAN DICTIONARY OBJEK MODEL KE DRIVE
# model_pkl_path = save_path + 'fitted_models_skripsi.pkl'
# with open(model_pkl_path, 'wb') as f:
#     pickle.dump(dict_fitted_models, f)

# # Simpan Dataframe Evaluasi Final ke CSV di GDrive
# csv_path = save_path + 'hasil_evaluasi_final_skripsi.csv'
# df_final_summary.to_csv(csv_path, index=False)

# print(f"\n✅ Model dan rekap evaluasi final berhasil disimpan di: {model_pkl_path} dan {csv_path}")
# # 1. Inisialisasi dictionary
# if platform == "gcolab":
#     dict_master_results = {} # Mulai dengan dictionary kosong di Colab
#     print("Inisialisasi dict_master_results kosong di Google Colab.")
    
#     for c_type in ["cpu","gpu"]:
#         file_path = prefix_path + f'pickle/eksperimen_skripsi_backup_{c_type}.pkl'
#         if os.path.exists(file_path):
#             with open(file_path, 'rb') as f:
#                 data = pickle.load(f)
#                 dict_master_results.update(data) # Menggabungkan dictionary
#                 print(f"Berhasil me-load data backup {c_type.upper()} ({len(data)} eksperimen).")
#         else:
#             print(f"File backup {c_type.upper()} tidak ditemukan di: {file_path}")

# elif platform == "kaggle": # Kaggle

#     # kalau pakai cpu yang dibuka gpu karena cpu sudah ada di var
#     # begitu pula sebaliknya
#     if compute_type == 'cpu':
#         file_path = prefix_path + f'pickle/eksperimen_skripsi_backup_gpu.pkl'
#     elif compute_type == 'gpu':
#         file_path = prefix_path + f'pickle/eksperimen_skripsi_backup_cpu.pkl'
    
#     if os.path.exists(file_path):
#         with open(file_path, 'rb') as f:
#             data = pickle.load(f)
#             dict_master_results.update(data) # Menggabungkan dictionary
#             print(f"Berhasil me-load backup SELAIN {compute_type.upper()} dengan {len(data)} eksperimen.")
#     else:
#         print(f"File backup {compute_type.upper()} tidak ditemukan di: {file_path}")


# print(f"Total seluruh eksperimen yang siap dievaluasi: {len(dict_master_results)}")

# # Mapping string nama model ke Class Modelnya
# dict_model_classes = {
#     'RandomForest': RandomForestClassifier,
#     'XGBoost': XGBClassifier,
#     'LightGBM': LGBMClassifier,
#     'AdaBoost': AdaBoostClassifier,
#     'CatBoost': CatBoostClassifier
# }

# # List untuk menyimpan rekapan performa Test dari SEMUA eksperimen
# final_evaluation_results = []

# # Mnampung metrik kurva PR per target
# pr_auc_plot_data = {'ppk': [], 'negeq': [], 'conloss': []}

# # dict menampung detail prediksi tiap baris data
# dict_detailed_predictions = {}

# # dict untuk menyimpan model yang udah difit
# dict_fitted_models = {}

# print("========== MEMULAI EVALUASI FINAL SELURUH EKSPERIMEN ==========")

# # Looping ke seluruh hasil eksperimen yang ada di master
# for exp_id, result_data in dict_master_results.items():
#     print(f"\nMengevaluasi: {exp_id}")
    
#     # 1. Parsing Konfigurasi dari exp_id (Format: target_nstrat_fstrat_model)
#     parts = exp_id.split('_')
#     t_name, n_strat, f_strat, m_name = parts[0], parts[1], parts[2], parts[3]
    
#     # 2. Ambil parameter terbaik & Class Model
#     best_params = result_data['best_params']
#     model_class = dict_model_classes[m_name]

#     # Atur deterministic parameter tambahan agar hasil konsisten
#     if model_class is RandomForestClassifier:
#         best_params['n_jobs'] = 1
#         best_params['random_state'] = 42
#     elif model_class is XGBClassifier:
#         best_params['n_jobs'] = 1
#     elif model_class is LGBMClassifier:
#         best_params['deterministic'] = True
#         best_params['force_col_wise'] = True
#         best_params['n_jobs'] = 1 
#     elif model_class is CatBoostClassifier:
#         best_params['thread_count'] = 1
#         best_params['random_seed'] = 42
    
#     # 3. Tarik Data Raw (Train utuh dan Test masa depan)
#     X_train_final = dict_data_splits[t_name]['X_train'].copy()
#     y_train_final = dict_data_splits[t_name]['y_train'].copy()
#     X_test_final = dict_data_splits[t_name]['X_test'].copy()
#     y_test_final = dict_data_splits[t_name]['y_test'].copy()
    
#     # --- 4. REPLIKASI PREPROCESSING (SESUAI KONFIGURASI) ---
    
#     # A. Missing Indicator & Imputasi
#     if n_strat == 'impute':
#         # Tambah missing indicator (Train dan Test)
#         cols_to_flag = kolom_missing_indicator[t_name]
#         for col in cols_to_flag:
#             X_train_final[f"{col}_is_missing"] = X_train_final[col].isnull().astype(int)
#             X_test_final[f"{col}_is_missing"] = X_test_final[col].isnull().astype(int)
            
#         # Terapkan Custom Imputation
#         fitur_cols = [c for c in X_train_final.columns if c not in metadata_cols]
#         X_train_final, X_test_final = apply_custom_imputation(X_train_final, X_test_final, fitur_cols)

#     # Simpan metadata untuk nanti digabung kembali jika diperlukan (misal untuk PCA yang butuh fitur asli)
#     meta_tr = X_train_final[metadata_cols].reset_index(drop=True)
#     meta_ts = X_test_final[metadata_cols].reset_index(drop=True)
        
#     # B. Strategi Fitur (Correlation Drop / PCA)
#     if f_strat == 'corr':
#         # Cari fitur korelasi tinggi HANYA di Train, drop di Train dan Test
#         cols_to_drop = get_low_corr_features_target_aware(X_train_final, y_train_final, threshold=0.85)
#         X_train_final = X_train_final.drop(columns=cols_to_drop)
#         X_test_final = X_test_final.drop(columns=cols_to_drop)
        
#     elif f_strat == 'pca':
#         fitur_cols = [c for c in X_train_final.columns if c not in metadata_cols]

#         # Buang Fitur Konstan dulu Sebelum PCA (agar PCA tidak error)
#         constant_cols = [col for col in fitur_cols if X_train_final[col].nunique() <= 1]
#         if constant_cols:
#             X_train_final = X_train_final.drop(columns=constant_cols)
#             X_test_final = X_test_final.drop(columns=constant_cols)
#             fitur_cols = [c for c in fitur_cols if c not in constant_cols]

#         scaler = StandardScaler()
#         pca = PCA(n_components=0.95, random_state=42)
        
#         # Fit Transform di Train, Transform di Test
#         X_tr_pca = pca.fit_transform(scaler.fit_transform(X_train_final[fitur_cols]))
#         X_ts_pca = pca.transform(scaler.transform(X_test_final[fitur_cols]))
        
#         # Rekonstruksi DataFrame (tempelkan metadata lagi)
#         pca_cols = [f"PC_{i}" for i in range(X_tr_pca.shape[1])]
#         X_train_final = pd.concat([meta_tr, pd.DataFrame(X_tr_pca, columns=pca_cols)], axis=1)
#         X_test_final = pd.concat([meta_ts, pd.DataFrame(X_ts_pca, columns=pca_cols)], axis=1)

#     # C. Drop Metadata sebelum masuk ke model
#     X_train_clean = X_train_final.drop(columns=metadata_cols, errors='ignore')
#     X_test_clean = X_test_final.drop(columns=metadata_cols, errors='ignore')
    
#     # --- 5. TRAINING MODEL FINAL & PREDIKSI ---
#     model = model_class(**best_params)
#     model.fit(X_train_clean, y_train_final)

#     # simpan model
#     dict_fitted_models[exp_id] = model
    
#     # Prediksi probabilitas pada Test Set
#     y_pred_probs = model.predict_proba(X_test_clean)[:, 1]
    
#     # Hitung Test PR-AUC
#     test_pr_auc = average_precision_score(y_test_final, y_pred_probs)
    
#     # --- 6. PENENTUAN THRESHOLD (PRIORITAS RECALL) ---
#     precisions, recalls, thresholds = precision_recall_curve(y_test_final, y_pred_probs)
#     target_recall = 0.80 

#     try:
#         # Cari threshold tertinggi yang recall-nya masih >= 80%
#         optimal_idx = np.where(recalls >= target_recall)[0][-1]
#         if optimal_idx >= len(thresholds):
#             optimal_idx = len(thresholds) - 1
#         best_threshold = thresholds[optimal_idx]
#         expected_precision = precisions[optimal_idx]
#         achieved_recall = recalls[optimal_idx]
#     except IndexError:
#         # Fallback jika model sangat buruk dan tidak bisa mencapai 80% recall
#         best_threshold = 0.5
#         expected_precision = 0
#         achieved_recall = 0
        
#     # Terapkan Threshold
#     y_pred_final = (y_pred_probs >= best_threshold).astype(int)

#     cm = confusion_matrix(y_test_final, y_pred_final)

#     # Simpan detail prediksi dan metadata ke dalam keranjang
#     dict_detailed_predictions[exp_id] = {
#         'metadata': meta_ts.copy(),
#         'y_true': y_test_final.reset_index(drop=True),
#         'y_pred': pd.Series(y_pred_final),
#         'y_prob': pd.Series(y_pred_probs),
#         'X_test_features': X_test_clean.reset_index(drop=True) # simpan fitur
#     }
    
#     # --- 7. SIMPAN HASIL KE REKAPAN ---
#     final_evaluation_results.append({
#         'Experiment_ID': exp_id,
#         'Target': t_name,
#         'Null_Strat': n_strat,
#         'Feat_Strat': f_strat,
#         'Model': m_name,
#         'Optuna_PR_AUC': result_data['best_optuna_pr_auc'], # Dari Optuna
#         'Test_PR_AUC': test_pr_auc,                 # Di Test Set riil
#         'Best_Threshold': best_threshold,
#         'Test_Recall': achieved_recall,
#         'Test_Precision': expected_precision,
#         'Confusion_Matrix': cm.tolist()
#     })
    
#     # # Cetak matriks hanya untuk model dengan PR_AUC Test > 0.40 agar layar tidak penuh
#     # if test_pr_auc > 0.40:
#     #     print(f"-> Test PR-AUC = {test_pr_auc:.4f} | Threshold = {best_threshold:.4f}")
#     #     cm = confusion_matrix(y_test_final, y_pred_final)
#     #     plt.figure(figsize=(4,3))
#     #     sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')
#     #     plt.title(f'{exp_id}')
#     #     plt.show()

#     # Simpan metrik kurva untuk diplot di luar loop
#     if t_name in pr_auc_plot_data:
#         pr_auc_plot_data[t_name].append({
#             'label': f"{n_strat}_{f_strat}_{m_name} (AUC={test_pr_auc:.3f})",
#             'recalls': recalls,
#             'precisions': precisions,
#             'auc': test_pr_auc
#         })

# # Jadikan DataFrame agar mudah diurutkan dan dianalisis
# df_final_summary = pd.DataFrame(final_evaluation_results)
# df_final_summary = df_final_summary.sort_values(by=['Target', 'Test_Precision'], ascending=[True, False]).reset_index(drop=True)

# print("\n========== REKAPAN PERINGKAT MODEL TERBAIK TIAP TARGET ==========")
# display(df_final_summary.head(3))

# # SIMPAN DICTIONARY OBJEK MODEL KE DRIVE
# model_pkl_path = save_path + 'fitted_models_skripsi.pkl'
# with open(model_pkl_path, 'wb') as f:
#     pickle.dump(dict_fitted_models, f)

# # Simpan Dataframe Evaluasi Final ke CSV di GDrive
# csv_path = save_path + 'hasil_evaluasi_final_skripsi.csv'
# df_final_summary.to_csv(csv_path, index=False)

# print(f"\n✅ Model dan rekap evaluasi final berhasil disimpan di: {model_pkl_path} dan {csv_path}")

PR AUC

import matplotlib.cm as mpl_cm
import numpy as np
import matplotlib.pyplot as plt

# ======================================================================
# --- VISUALISASI KURVA PR-AUC GABUNGAN PER TARGET (HORIZONTAL R-TO-L) ---
# ======================================================================

# 1. Filter target yang valid & memiliki data curves
valid_targets = [(t_name, curves) for t_name, curves in pr_auc_plot_data.items() if len(curves) > 0][::-1]
n_targets = len(valid_targets)

if n_targets > 0:
    # 2. Inisialisasi Kanvas Multi-Plot
    # Lebar diturunkan menjadi 8 per plot agar jarak antar kotak merapat
    # Tinggi dinaikkan sedikit ke 15 agar seimbang dengan legenda di bawahnya
    fig, axes = plt.subplots(nrows=1, ncols=n_targets, figsize=(8 * n_targets, 15))

    if n_targets == 1:
        axes = [axes]

    # 3. Looping untuk mengisi plot dari kanan ke kiri
    for i, (target_name, curves) in enumerate(valid_targets):
        ax = axes[n_targets - 1 - i]

        # Urutkan berdasarkan AUC tertinggi agar garis dan legend rapi dari atas ke bawah
        curves = sorted(curves, key=lambda x: x['auc'], reverse=True)

        # Hitung jumlah konfigurasi masing-masing
        keep_count = sum(1 for c in curves if 'keep' in c['label'].lower())
        impute_count = sum(1 for c in curves if 'impute' in c['label'].lower())

        # Rentang dibalik (0.9, 0.4) agar list dimulai dari warna paling tua ke muda
        green_colors = mpl_cm.Greens(np.linspace(0.9, 0.4, keep_count)) if keep_count > 0 else []
        red_colors = mpl_cm.Reds(np.linspace(0.9, 0.4, impute_count)) if impute_count > 0 else []
        
        green_iter = iter(green_colors)
        red_iter = iter(red_colors)
        
        # Plot seluruh konfigurasi
        for curve in curves:
            label_lower = curve['label'].lower()
            
            if 'keep' in label_lower:
                line_color = next(green_iter)
            elif 'impute' in label_lower:
                line_color = next(red_iter)
            else:
                line_color = 'gray'
                
            ax.plot(curve['recalls'], curve['precisions'], label=curve['label'], 
                    color=line_color, alpha=0.8, linewidth=2.5)
            
        # 4. Mengatur Judul & Label (Tetap diukuran raksasa)
        ax.set_title(f'Target: {target_name.upper()}', fontsize=24, fontweight='bold', pad=10)
        ax.set_xlabel('Recall', fontsize=20)
        ax.set_ylabel('Precision', fontsize=20)
        ax.tick_params(axis='both', labelsize=16)
        ax.grid(True, linestyle='--', alpha=0.5)
        
        # Memaksa kotak plot untuk selalu berbentuk persegi (1:1)
        ax.set_box_aspect(1)
        
        # Menggunakan ncol=1 untuk mengembalikan layout legenda menjadi 1 kolom vertikal
        ax.legend(bbox_to_anchor=(0.5, -0.15), loc='upper center', borderaxespad=0., fontsize=16, ncol=1)
    
    # 5. Mengatur Judul Utama Global
    fig.suptitle('Precision-Recall Curve', fontsize=32, fontweight='bold', y=0.97)
    
    # --- PERBAIKAN TATA LETAK ---
    # Tambahkan w_pad=1.0 untuk memastikan jarak horizontal antar plot saling merapat
    plt.tight_layout(rect=[0, 0.0, 1, 0.95], w_pad=1.0)
    plt.show()

# ======================================================================
# --- VISUALISASI KURVA PR-AUC GABUNGAN PER TARGET ---
# ======================================================================
for target_name, curves in pr_auc_plot_data.items():
    if len(curves) == 0:
        continue
        
    # Urutkan berdasarkan AUC tertinggi agar legend rapi
    curves = sorted(curves, key=lambda x: x['auc'], reverse=True)
    
    plt.figure(figsize=(10, 7))
    for curve in curves:
        # Menambahkan transparansi (alpha) agar garis yang bertumpuk tetap terlihat
        plt.plot(curve['recalls'], curve['precisions'], label=curve['label'], alpha=0.7, linewidth=1.5)
        
    plt.title(f'Precision-Recall Curve (All Configurations) - Target: {target_name.upper()}')
    plt.xlabel('Recall')
    plt.ylabel('Precision')
    
    # Memaksa legend berada di luar grafik (sebelah kanan) agar tidak menutupi kurva
    plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0., fontsize=8)
    plt.grid(True, linestyle='--', alpha=0.5)
    plt.tight_layout()
    plt.show()

ROC AUC

import matplotlib.cm as mpl_cm
import numpy as np
import matplotlib.pyplot as plt
from sklearn.metrics import roc_curve, roc_auc_score

# 1. Definisikan list target dan siapkan wadah untuk data yang sudah diproses
daftar_target = ['conloss', 'negeq', 'ppk']
valid_roc_data = []

# --- TAHAP 1: PENGUMPULAN & PERHITUNGAN DATA ---
for target_name in daftar_target:
    curves_roc = []
    
    # Kumpulkan seluruh eksperimen yang termasuk dalam target ini
    for exp_id, data in dict_detailed_predictions.items():
        parts = exp_id.split('_')
        t_name = parts[0]  
        
        if t_name == target_name:
            y_true = data['y_true']
            y_prob = data['y_prob']
            
            # Hitung metrik ROC secara dinamis
            fpr, tpr, _ = roc_curve(y_true, y_prob)
            roc_auc = roc_auc_score(y_true, y_prob)
            
            # Format label agar hanya menampilkan konfigurasi
            config_label = "_".join(parts[1:])
            
            curves_roc.append({
                'label': f"{config_label} (AUC={roc_auc:.3f})",
                'fpr': fpr,
                'tpr': tpr,
                'auc': roc_auc
            })
            
    # Jika ada data, urutkan dan simpan ke list utama
    if len(curves_roc) > 0:
        curves_roc = sorted(curves_roc, key=lambda x: x['auc'], reverse=True)
        valid_roc_data.append((target_name, curves_roc))

# --- TAHAP 2: VISUALISASI ---
n_targets = len(valid_roc_data)

if n_targets > 0:
    # 2. Inisialisasi Kanvas Multi-Plot
    # Lebar diset 8 per plot, Tinggi diset 15 agar seimbang dengan legenda
    fig, axes = plt.subplots(nrows=1, ncols=n_targets, figsize=(8 * n_targets, 15))
    
    if n_targets == 1:
        axes = [axes]
        
    # 3. Looping untuk mengisi plot dari kanan ke kiri
    for i, (target_name, curves_roc) in enumerate(valid_roc_data):
        # Trik Alur Kanan ke Kiri: Indeks dibalik
        ax = axes[n_targets - 1 - i]
        
        # --- LOGIKA PEWARNAAN GARIS ---
        # Hitung jumlah konfigurasi masing-masing
        keep_count = sum(1 for c in curves_roc if 'keep' in c['label'].lower())
        impute_count = sum(1 for c in curves_roc if 'impute' in c['label'].lower())
        
        # Rentang dibalik (0.9, 0.4) agar list dimulai dari warna paling tua ke muda
        green_colors = mpl_cm.Greens(np.linspace(0.9, 0.4, keep_count)) if keep_count > 0 else []
        red_colors = mpl_cm.Reds(np.linspace(0.9, 0.4, impute_count)) if impute_count > 0 else []
        
        green_iter = iter(green_colors)
        red_iter = iter(red_colors)
        
        # Plot seluruh konfigurasi
        for curve in curves_roc:
            label_lower = curve['label'].lower()
            
            if 'keep' in label_lower:
                line_color = next(green_iter)
            elif 'impute' in label_lower:
                line_color = next(red_iter)
            else:
                line_color = 'gray'
                
            ax.plot(curve['fpr'], curve['tpr'], label=curve['label'], 
                    color=line_color, alpha=0.8, linewidth=2.5)
            
        # Tambahkan garis diagonal baseline (Random Guessing) disamakan ketebalannya
        ax.plot([0, 1], [0, 1], color='black', linestyle='--', alpha=0.5, label='Baseline (AUC=0.500)', linewidth=2.5)
        
        # 4. Mengatur Judul Kecil & Label per Sub-Plot (UKURAN RAKSASA)
        ax.set_title(f'Target: {target_name.upper()}', fontsize=24, fontweight='bold', pad=10)
        ax.set_xlabel('False Positive Rate', fontsize=20)
        ax.set_ylabel('True Positive Rate', fontsize=20)
        ax.tick_params(axis='both', labelsize=16)
        ax.grid(True, linestyle='--', alpha=0.5)
        
        # Memaksa kotak plot untuk selalu berbentuk persegi (1:1)
        ax.set_box_aspect(1)
        
        # Meletakkan legenda di tengah bawah dengan 1 kolom
        ax.legend(bbox_to_anchor=(0.5, -0.15), loc='upper center', borderaxespad=0., fontsize=16, ncol=1)
        
    # 5. Mengatur Judul Utama Global
    fig.suptitle('ROC-AUC Curve', fontsize=32, fontweight='bold', y=0.97)
    
    # --- PERBAIKAN TATA LETAK ---
    plt.tight_layout(rect=[0, 0.0, 1, 0.95], w_pad=1.0)
    plt.show()

# 1. Definisikan list target yang dimiliki
daftar_target = ['ppk', 'negeq', 'conloss']

# 2. Iterasi per tipe target (Satu target = Satu Kanvas Gambar)
for target_name in daftar_target:
    curves_roc = []
    
    # Kumpulkan seluruh eksperimen yang termasuk dalam target ini
    for exp_id, data in dict_detailed_predictions.items():
        parts = exp_id.split('_')
        t_name = parts[0]  # Mengambil 'ppk', 'negeq', atau 'conloss'
        
        if t_name == target_name:
            y_true = data['y_true']
            y_prob = data['y_prob']
            
            # Hitung metrik ROC secara dinamis
            fpr, tpr, _ = roc_curve(y_true, y_prob)
            roc_auc = roc_auc_score(y_true, y_prob)
            
            # Format label agar hanya menampilkan konfigurasi (misal: impute_corr_XGBoost)
            config_label = "_".join(parts[1:])
            
            curves_roc.append({
                'label': f"{config_label} (AUC={roc_auc:.3f})",
                'fpr': fpr,
                'tpr': tpr,
                'auc': roc_auc
            })
            
    # Jika tidak ada eksperimen untuk target ini, lewati
    if len(curves_roc) == 0:
        continue
        
    # Urutkan dari nilai AUC tertinggi agar urutan legenda rapi
    curves_roc = sorted(curves_roc, key=lambda x: x['auc'], reverse=True)
    
    # 3. Mulai Plotting per Target
    plt.figure(figsize=(10, 7))
    for curve in curves_roc:
        # Menampilkan tiap kombinasi konfigurasi sebagai garis berbeda di kanvas yang sama
        plt.plot(curve['fpr'], curve['tpr'], label=curve['label'], alpha=0.7, linewidth=1.5)
        
    # Tambahkan garis diagonal baseline (Random Guessing)
    plt.plot([0, 1], [0, 1], color='black', linestyle='--', alpha=0.5, label='Baseline (AUC=0.500)')
    
    plt.title(f'ROC-AUC Curve (All Configurations) - Target: {target_name.upper()}', fontsize=12, fontweight='bold')
    plt.xlabel('False Positive Rate (1 - Specificity)')
    plt.ylabel('True Positive Rate (Sensitivity / Recall)')
    
    # Memaksa legend berada di luar grafik sebelah kanan (sama seperti PR-AUC)
    plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0., fontsize=8)
    plt.grid(True, linestyle='--', alpha=0.5)
    plt.tight_layout()
    plt.show()

print("========== REKAPAN NILAI ROC AUC ==========")

for exp_id, data in dict_detailed_predictions.items():
    y_true = data['y_true']
    y_prob = data['y_prob']
    
    # Hitung nilai ROC AUC
    roc_auc = roc_auc_score(y_true, y_prob)
    
    # Cetak sesuai format yang diminta (dibulatkan 3 angka di belakang koma)
    print(f"- ROC AUC {exp_id} = {roc_auc:.3f}")
========== REKAPAN NILAI ROC AUC ==========
- ROC AUC ppk_keep_all_RandomForest = 0.848
- ROC AUC ppk_keep_corr_RandomForest = 0.843
- ROC AUC ppk_impute_all_RandomForest = 0.512
- ROC AUC ppk_impute_all_AdaBoost = 0.447
- ROC AUC ppk_impute_corr_RandomForest = 0.491
- ROC AUC ppk_impute_corr_AdaBoost = 0.481
- ROC AUC ppk_impute_pca_RandomForest = 0.523
- ROC AUC ppk_impute_pca_AdaBoost = 0.525
- ROC AUC negeq_keep_all_RandomForest = 0.931
- ROC AUC negeq_keep_corr_RandomForest = 0.900
- ROC AUC negeq_impute_all_RandomForest = 0.392
- ROC AUC negeq_impute_all_AdaBoost = 0.490
- ROC AUC negeq_impute_corr_RandomForest = 0.395
- ROC AUC negeq_impute_corr_AdaBoost = 0.508
- ROC AUC negeq_impute_pca_RandomForest = 0.464
- ROC AUC negeq_impute_pca_AdaBoost = 0.595
- ROC AUC conloss_keep_all_RandomForest = 0.913
- ROC AUC conloss_keep_corr_RandomForest = 0.912
- ROC AUC conloss_impute_all_RandomForest = 0.525
- ROC AUC conloss_impute_all_AdaBoost = 0.479
- ROC AUC conloss_impute_corr_RandomForest = 0.524
- ROC AUC conloss_impute_corr_AdaBoost = 0.480
- ROC AUC conloss_impute_pca_RandomForest = 0.528
- ROC AUC conloss_impute_pca_AdaBoost = 0.517
- ROC AUC ppk_keep_all_XGBoost = 0.832
- ROC AUC ppk_keep_all_LightGBM = 0.819
- ROC AUC ppk_keep_all_CatBoost = 0.839
- ROC AUC ppk_keep_corr_XGBoost = 0.830
- ROC AUC ppk_keep_corr_LightGBM = 0.822
- ROC AUC ppk_keep_corr_CatBoost = 0.834
- ROC AUC ppk_impute_all_XGBoost = 0.442
- ROC AUC ppk_impute_all_LightGBM = 0.464
- ROC AUC ppk_impute_all_CatBoost = 0.432
- ROC AUC ppk_impute_corr_XGBoost = 0.469
- ROC AUC ppk_impute_corr_LightGBM = 0.459
- ROC AUC ppk_impute_corr_CatBoost = 0.458
- ROC AUC ppk_impute_pca_XGBoost = 0.513
- ROC AUC ppk_impute_pca_LightGBM = 0.490
- ROC AUC ppk_impute_pca_CatBoost = 0.493
- ROC AUC negeq_keep_all_XGBoost = 0.894
- ROC AUC negeq_keep_all_LightGBM = 0.855
- ROC AUC negeq_keep_all_CatBoost = 0.931
- ROC AUC negeq_keep_corr_XGBoost = 0.890
- ROC AUC negeq_keep_corr_LightGBM = 0.835
- ROC AUC negeq_keep_corr_CatBoost = 0.903
- ROC AUC negeq_impute_all_XGBoost = 0.444
- ROC AUC negeq_impute_all_LightGBM = 0.464
- ROC AUC negeq_impute_all_CatBoost = 0.425
- ROC AUC negeq_impute_corr_XGBoost = 0.470
- ROC AUC negeq_impute_corr_LightGBM = 0.431
- ROC AUC negeq_impute_corr_CatBoost = 0.470
- ROC AUC negeq_impute_pca_XGBoost = 0.519
- ROC AUC negeq_impute_pca_LightGBM = 0.526
- ROC AUC negeq_impute_pca_CatBoost = 0.486
- ROC AUC conloss_keep_all_XGBoost = 0.918
- ROC AUC conloss_keep_all_LightGBM = 0.916
- ROC AUC conloss_keep_all_CatBoost = 0.910
- ROC AUC conloss_keep_corr_XGBoost = 0.915
- ROC AUC conloss_keep_corr_LightGBM = 0.905
- ROC AUC conloss_keep_corr_CatBoost = 0.917
- ROC AUC conloss_impute_all_XGBoost = 0.506
- ROC AUC conloss_impute_all_LightGBM = 0.497
- ROC AUC conloss_impute_all_CatBoost = 0.501
- ROC AUC conloss_impute_corr_XGBoost = 0.480
- ROC AUC conloss_impute_corr_LightGBM = 0.484
- ROC AUC conloss_impute_corr_CatBoost = 0.494
- ROC AUC conloss_impute_pca_XGBoost = 0.508
- ROC AUC conloss_impute_pca_LightGBM = 0.502
- ROC AUC conloss_impute_pca_CatBoost = 0.481

Evaluasi

Hasil Asesmen Semua Konfigurasi Model

with pd.option_context('display.max_rows', None, 'display.max_columns', None):
    display(df_final_summary)
Experiment_ID Target Null_Strat Feat_Strat Model Optuna_PR_AUC Test_PR_AUC Best_Threshold Test_Recall Test_Precision Confusion_Matrix Test_Specificity Test_F1 Test_Accuracy
0 conloss_keep_corr_CatBoost conloss keep corr CatBoost 0.579363 0.624740 0.058335 0.949367 0.307377 [[731, 338], [8, 150]] 0.683817 0.464396 0.718011
1 conloss_keep_all_XGBoost conloss keep all XGBoost 0.571422 0.623831 0.060684 0.943038 0.299197 [[720, 349], [9, 149]] 0.673527 0.454268 0.708231
2 conloss_keep_corr_LightGBM conloss keep corr LightGBM 0.567439 0.623410 0.002667 0.936709 0.273063 [[675, 394], [10, 148]] 0.631431 0.422857 0.670742
3 conloss_keep_corr_XGBoost conloss keep corr XGBoost 0.566969 0.617837 0.062856 0.949367 0.295276 [[711, 358], [8, 150]] 0.665108 0.450450 0.701711
4 conloss_keep_all_LightGBM conloss keep all LightGBM 0.566194 0.609190 0.023756 0.936709 0.282983 [[694, 375], [10, 148]] 0.649205 0.434655 0.686227
5 conloss_keep_all_CatBoost conloss keep all CatBoost 0.583181 0.599378 0.043155 0.943038 0.272395 [[671, 398], [9, 149]] 0.627689 0.422695 0.668297
6 conloss_keep_all_RandomForest conloss keep all RandomForest 0.544012 0.593263 0.103132 0.962025 0.298039 [[711, 358], [6, 152]] 0.665108 0.455090 0.703341
7 conloss_keep_corr_RandomForest conloss keep corr RandomForest 0.542281 0.560232 0.094077 0.962025 0.294004 [[704, 365], [6, 152]] 0.658559 0.450370 0.697637
8 conloss_impute_pca_AdaBoost conloss impute pca AdaBoost 0.185754 0.159655 0.395077 0.164557 0.189781 [[958, 111], [132, 26]] 0.896165 0.176271 0.801956
9 conloss_impute_corr_RandomForest conloss impute corr RandomForest 0.236535 0.154020 0.076328 0.968354 0.128788 [[34, 1035], [5, 153]] 0.031805 0.227340 0.152404
10 conloss_impute_all_RandomForest conloss impute all RandomForest 0.227192 0.152983 0.076892 0.974684 0.129086 [[30, 1039], [4, 154]] 0.028064 0.227979 0.149959
11 conloss_impute_pca_RandomForest conloss impute pca RandomForest 0.193522 0.147872 0.074176 0.829114 0.126692 [[166, 903], [27, 131]] 0.155285 0.219799 0.242054
12 conloss_impute_all_XGBoost conloss impute all XGBoost 0.216323 0.145872 0.076350 1.000000 0.128769 [[0, 1069], [0, 158]] 0.000000 0.228159 0.128769
13 conloss_impute_pca_CatBoost conloss impute pca CatBoost 0.178360 0.138323 0.072692 0.898734 0.126447 [[88, 981], [16, 142]] 0.082320 0.221702 0.187449
14 conloss_impute_corr_CatBoost conloss impute corr CatBoost 0.212818 0.135268 0.083807 0.968354 0.127076 [[18, 1051], [5, 153]] 0.016838 0.224670 0.139364
15 conloss_impute_all_CatBoost conloss impute all CatBoost 0.205887 0.135089 0.081985 0.974684 0.127907 [[19, 1050], [4, 154]] 0.017774 0.226138 0.140994
16 conloss_impute_pca_XGBoost conloss impute pca XGBoost 0.173743 0.133132 0.024094 0.829114 0.129832 [[191, 878], [27, 131]] 0.178672 0.224507 0.262429
17 conloss_impute_pca_LightGBM conloss impute pca LightGBM 0.171470 0.130777 0.001406 0.968354 0.130324 [[48, 1021], [5, 153]] 0.044902 0.229730 0.163814
18 conloss_impute_corr_LightGBM conloss impute corr LightGBM 0.210543 0.130056 0.078020 1.000000 0.128769 [[0, 1069], [0, 158]] 0.000000 0.228159 0.128769
19 conloss_impute_all_AdaBoost conloss impute all AdaBoost 0.191228 0.129227 0.162216 0.803797 0.126747 [[194, 875], [31, 127]] 0.181478 0.218966 0.261614
20 conloss_impute_all_LightGBM conloss impute all LightGBM 0.207482 0.129117 0.077203 1.000000 0.128769 [[0, 1069], [0, 158]] 0.000000 0.228159 0.128769
21 conloss_impute_corr_XGBoost conloss impute corr XGBoost 0.218250 0.127453 0.078306 1.000000 0.128769 [[0, 1069], [0, 158]] 0.000000 0.228159 0.128769
22 conloss_impute_corr_AdaBoost conloss impute corr AdaBoost 0.185537 0.125277 0.180465 0.848101 0.127863 [[155, 914], [24, 134]] 0.144995 0.222222 0.235534
23 negeq_keep_all_RandomForest negeq keep all RandomForest 0.417147 0.467442 0.059480 0.852941 0.166667 [[1609, 145], [5, 29]] 0.917332 0.278846 0.916107
24 negeq_keep_corr_RandomForest negeq keep corr RandomForest 0.446320 0.458193 0.050467 0.852941 0.122881 [[1547, 207], [5, 29]] 0.881984 0.214815 0.881432
25 negeq_keep_all_CatBoost negeq keep all CatBoost 0.529702 0.404039 0.012689 0.823529 0.142132 [[1585, 169], [6, 28]] 0.903649 0.242424 0.902125
26 negeq_keep_corr_CatBoost negeq keep corr CatBoost 0.498662 0.381612 0.007394 0.794118 0.120536 [[1557, 197], [7, 27]] 0.887685 0.209302 0.885906
27 negeq_keep_all_LightGBM negeq keep all LightGBM 0.514100 0.303942 0.000840 0.735294 0.058005 [[1348, 406], [9, 25]] 0.768529 0.107527 0.767897
28 negeq_keep_corr_LightGBM negeq keep corr LightGBM 0.495929 0.299926 0.000132 0.764706 0.052953 [[1289, 465], [8, 26]] 0.734892 0.099048 0.735459
29 negeq_keep_corr_XGBoost negeq keep corr XGBoost 0.428743 0.269452 0.011085 0.705882 0.114286 [[1568, 186], [10, 24]] 0.893957 0.196721 0.890380
30 negeq_keep_all_XGBoost negeq keep all XGBoost 0.426746 0.265196 0.011342 0.676471 0.113861 [[1575, 179], [11, 23]] 0.897948 0.194915 0.893736
31 negeq_impute_all_AdaBoost negeq impute all AdaBoost 0.090062 0.026135 0.345297 0.441176 0.018007 [[936, 818], [19, 15]] 0.533637 0.034602 0.531879
32 negeq_impute_pca_AdaBoost negeq impute pca AdaBoost 0.084748 0.025583 0.249826 0.000000 0.000000 [[1753, 1], [34, 0]] 0.999430 0.000000 0.980425
33 negeq_impute_pca_XGBoost negeq impute pca XGBoost 0.081999 0.020723 0.008357 0.970588 0.019550 [[99, 1655], [1, 33]] 0.056442 0.038328 0.073826
34 negeq_impute_pca_LightGBM negeq impute pca LightGBM 0.094666 0.020156 0.007492 1.000000 0.019016 [[0, 1754], [0, 34]] 0.000000 0.037322 0.019016
35 negeq_impute_corr_AdaBoost negeq impute corr AdaBoost 0.084543 0.020000 0.390553 0.764706 0.019160 [[423, 1331], [8, 26]] 0.241163 0.037383 0.251119
36 negeq_impute_corr_CatBoost negeq impute corr CatBoost 0.098251 0.018241 0.111068 1.000000 0.019016 [[0, 1754], [0, 34]] 0.000000 0.037322 0.019016
37 negeq_impute_pca_CatBoost negeq impute pca CatBoost 0.108857 0.018042 0.027811 0.970588 0.019053 [[55, 1699], [1, 33]] 0.031357 0.037373 0.049217
38 negeq_impute_pca_RandomForest negeq impute pca RandomForest 0.119677 0.017961 0.009023 1.000000 0.019016 [[0, 1754], [0, 34]] 0.000000 0.037322 0.019016
39 negeq_impute_all_LightGBM negeq impute all LightGBM 0.089864 0.017722 0.000026 0.676471 0.018639 [[543, 1211], [11, 23]] 0.309578 0.036278 0.316555
40 negeq_impute_all_XGBoost negeq impute all XGBoost 0.110072 0.017494 0.008673 1.000000 0.019080 [[6, 1748], [0, 34]] 0.003421 0.037445 0.022371
41 negeq_impute_corr_XGBoost negeq impute corr XGBoost 0.106669 0.017146 0.004265 1.000000 0.019551 [[49, 1705], [0, 34]] 0.027936 0.038353 0.046421
42 negeq_impute_all_CatBoost negeq impute all CatBoost 0.097000 0.016889 0.020205 1.000000 0.019518 [[46, 1708], [0, 34]] 0.026226 0.038288 0.044743
43 negeq_impute_corr_LightGBM negeq impute corr LightGBM 0.107772 0.016014 0.000000 0.647059 0.017028 [[484, 1270], [12, 22]] 0.275941 0.033183 0.282998
44 negeq_impute_corr_RandomForest negeq impute corr RandomForest 0.109019 0.014907 0.008418 0.970588 0.018571 [[10, 1744], [1, 33]] 0.005701 0.036444 0.024049
45 negeq_impute_all_RandomForest negeq impute all RandomForest 0.110482 0.014780 0.008312 1.000000 0.019048 [[3, 1751], [0, 34]] 0.001710 0.037383 0.020694
46 ppk_keep_all_RandomForest ppk keep all RandomForest 0.500955 0.754347 0.102608 0.864078 0.504249 [[270, 175], [28, 178]] 0.606742 0.636852 0.688172
47 ppk_keep_corr_CatBoost ppk keep corr CatBoost 0.571880 0.747768 0.009481 0.883495 0.490566 [[256, 189], [24, 182]] 0.575281 0.630849 0.672811
48 ppk_keep_corr_RandomForest ppk keep corr RandomForest 0.516061 0.744832 0.097407 0.873786 0.520231 [[279, 166], [26, 180]] 0.626966 0.652174 0.705069
49 ppk_keep_all_CatBoost ppk keep all CatBoost 0.574503 0.739800 0.018137 0.898058 0.456790 [[225, 220], [21, 185]] 0.505618 0.605565 0.629800
50 ppk_keep_all_XGBoost ppk keep all XGBoost 0.548289 0.716612 0.015902 0.898058 0.469543 [[236, 209], [21, 185]] 0.530337 0.616667 0.646697
51 ppk_keep_corr_LightGBM ppk keep corr LightGBM 0.556769 0.710270 0.008526 0.941748 0.425439 [[183, 262], [12, 194]] 0.411236 0.586103 0.579109
52 ppk_keep_corr_XGBoost ppk keep corr XGBoost 0.542239 0.706544 0.030403 0.849515 0.513196 [[279, 166], [31, 175]] 0.626966 0.639854 0.697389
53 ppk_keep_all_LightGBM ppk keep all LightGBM 0.562097 0.705019 0.002538 0.898058 0.417607 [[187, 258], [21, 185]] 0.420225 0.570108 0.571429
54 ppk_impute_all_RandomForest ppk impute all RandomForest 0.369765 0.364025 0.071357 0.961165 0.316294 [[17, 428], [8, 198]] 0.038202 0.475962 0.330261
55 ppk_impute_corr_RandomForest ppk impute corr RandomForest 0.343327 0.349207 0.068764 1.000000 0.316436 [[0, 445], [0, 206]] 0.000000 0.480747 0.316436
56 ppk_impute_pca_RandomForest ppk impute pca RandomForest 0.339715 0.345245 0.110507 0.563107 0.324022 [[203, 242], [90, 116]] 0.456180 0.411348 0.490015
57 ppk_impute_pca_XGBoost ppk impute pca XGBoost 0.378230 0.331170 0.063258 0.655340 0.327670 [[168, 277], [71, 135]] 0.377528 0.436893 0.465438
58 ppk_impute_pca_AdaBoost ppk impute pca AdaBoost 0.310142 0.330281 0.429521 0.233010 0.317881 [[342, 103], [158, 48]] 0.768539 0.268908 0.599078
59 ppk_impute_pca_CatBoost ppk impute pca CatBoost 0.391594 0.329774 0.036931 0.713592 0.307531 [[114, 331], [59, 147]] 0.256180 0.429825 0.400922
60 ppk_impute_pca_LightGBM ppk impute pca LightGBM 0.383868 0.320410 0.001057 0.757282 0.317719 [[110, 335], [50, 156]] 0.247191 0.447633 0.408602
61 ppk_impute_corr_XGBoost ppk impute corr XGBoost 0.373046 0.312541 0.074998 1.000000 0.316436 [[0, 445], [0, 206]] 0.000000 0.480747 0.316436
62 ppk_impute_all_XGBoost ppk impute all XGBoost 0.381881 0.303941 0.012297 0.995146 0.315871 [[1, 444], [1, 205]] 0.002247 0.479532 0.316436
63 ppk_impute_corr_LightGBM ppk impute corr LightGBM 0.382905 0.303882 0.004447 0.995146 0.315385 [[0, 445], [1, 205]] 0.000000 0.478972 0.314900
64 ppk_impute_all_LightGBM ppk impute all LightGBM 0.401347 0.303398 0.000034 0.849515 0.303819 [[44, 401], [31, 175]] 0.098876 0.447570 0.336406
65 ppk_impute_corr_CatBoost ppk impute corr CatBoost 0.407648 0.301576 0.004015 0.975728 0.317035 [[12, 433], [5, 201]] 0.026966 0.478571 0.327189
66 ppk_impute_corr_AdaBoost ppk impute corr AdaBoost 0.320983 0.295450 0.270696 0.631068 0.323383 [[173, 272], [76, 130]] 0.388764 0.427632 0.465438
67 ppk_impute_all_CatBoost ppk impute all CatBoost 0.409952 0.294545 0.004135 0.985437 0.315217 [[4, 441], [3, 203]] 0.008989 0.477647 0.317972
68 ppk_impute_all_AdaBoost ppk impute all AdaBoost 0.310547 0.282853 0.352924 0.233010 0.247423 [[299, 146], [158, 48]] 0.671910 0.240000 0.533026
print(df_final_summary.to_csv(index=False))
Experiment_ID,Target,Null_Strat,Feat_Strat,Model,Optuna_PR_AUC,Test_PR_AUC,Best_Threshold,Test_Recall,Test_Precision,Confusion_Matrix,Test_Specificity,Test_F1,Test_Accuracy
conloss_keep_corr_CatBoost,conloss,keep,corr,CatBoost,0.5793629045077039,0.6247402413159118,0.05833520459810593,0.9493670886075949,0.3073770491803279,"[[731, 338], [8, 150]]",0.6838166510757717,0.46439628482972134,0.7180114099429503
conloss_keep_all_XGBoost,conloss,keep,all,XGBoost,0.5714215738607572,0.6238310558219972,0.06068401038646698,0.9430379746835443,0.2991967871485944,"[[720, 349], [9, 149]]",0.6735266604303087,0.45426829268292684,0.7082314588427058
conloss_keep_corr_LightGBM,conloss,keep,corr,LightGBM,0.5674394091033492,0.6234100557004398,0.0026665631522638364,0.9367088607594937,0.2730627306273063,"[[675, 394], [10, 148]]",0.6314312441534145,0.4228571428571429,0.6707416462917686
conloss_keep_corr_XGBoost,conloss,keep,corr,XGBoost,0.5669690192635685,0.6178372289627286,0.06285607069730759,0.9493670886075949,0.2952755905511811,"[[711, 358], [8, 150]]",0.6651075771749299,0.45045045045045046,0.7017114914425427
conloss_keep_all_LightGBM,conloss,keep,all,LightGBM,0.566194207254477,0.6091903176556698,0.023755998304761973,0.9367088607594937,0.2829827915869981,"[[694, 375], [10, 148]]",0.6492048643592142,0.434654919236417,0.6862265688671557
conloss_keep_all_CatBoost,conloss,keep,all,CatBoost,0.5831806230254756,0.5993776393815425,0.04315520980250645,0.9430379746835443,0.27239488117001825,"[[671, 398], [9, 149]]",0.627689429373246,0.4226950354609929,0.6682966585167074
conloss_keep_all_RandomForest,conloss,keep,all,RandomForest,0.5440121230786213,0.5932626457275421,0.10313216101867166,0.9620253164556962,0.2980392156862745,"[[711, 358], [6, 152]]",0.6651075771749299,0.4550898203592814,0.7033414832925835
conloss_keep_corr_RandomForest,conloss,keep,corr,RandomForest,0.5422811197304024,0.5602321293019342,0.09407666229288404,0.9620253164556962,0.2940038684719536,"[[704, 365], [6, 152]]",0.6585594013096352,0.45037037037037037,0.6976365118174409
conloss_impute_pca_AdaBoost,conloss,impute,pca,AdaBoost,0.1857544125047856,0.15965537559102566,0.3950771133294802,0.16455696202531644,0.1897810218978102,"[[958, 111], [132, 26]]",0.8961646398503275,0.17627118644067796,0.8019559902200489
conloss_impute_corr_RandomForest,conloss,impute,corr,RandomForest,0.23653467800639058,0.15402029646853876,0.07632841380226682,0.9683544303797469,0.12878787878787878,"[[34, 1035], [5, 153]]",0.031805425631431246,0.22734026745913818,0.15240423797881011
conloss_impute_all_RandomForest,conloss,impute,all,RandomForest,0.22719221241109172,0.15298346799248047,0.07689151930468276,0.9746835443037974,0.12908633696563285,"[[30, 1039], [4, 154]]",0.02806361085126286,0.22797927461139897,0.14995925020374898
conloss_impute_pca_RandomForest,conloss,impute,pca,RandomForest,0.19352154293774204,0.1478716269958704,0.07417635658914729,0.8291139240506329,0.12669245647969052,"[[166, 903], [27, 131]]",0.15528531337698784,0.2197986577181208,0.24205378973105135
conloss_impute_all_XGBoost,conloss,impute,all,XGBoost,0.21632282201956754,0.14587163460566516,0.07634969055652618,1.0,0.12876935615321924,"[[0, 1069], [0, 158]]",0.0,0.22815884476534296,0.12876935615321924
conloss_impute_pca_CatBoost,conloss,impute,pca,CatBoost,0.17835978340452197,0.13832338638815062,0.07269169798521348,0.8987341772151899,0.12644701691896706,"[[88, 981], [16, 142]]",0.0823199251637044,0.22170179547228727,0.18744906275468623
conloss_impute_corr_CatBoost,conloss,impute,corr,CatBoost,0.21281794433885776,0.13526834102997842,0.08380675600146752,0.9683544303797469,0.1270764119601329,"[[18, 1051], [5, 153]]",0.01683816651075772,0.22466960352422907,0.1393643031784841
conloss_impute_all_CatBoost,conloss,impute,all,CatBoost,0.20588719720895562,0.13508852089090406,0.0819846305283936,0.9746835443037974,0.12790697674418605,"[[19, 1050], [4, 154]]",0.01777362020579981,0.2261380323054332,0.14099429502852487
conloss_impute_pca_XGBoost,conloss,impute,pca,XGBoost,0.17374300825837827,0.1331315465042961,0.024094466120004654,0.8291139240506329,0.12983151635282458,"[[191, 878], [27, 131]]",0.17867165575304023,0.22450728363324765,0.2624286878565607
conloss_impute_pca_LightGBM,conloss,impute,pca,LightGBM,0.1714697473030024,0.13077675434049707,0.0014064269861669264,0.9683544303797469,0.1303236797274276,"[[48, 1021], [5, 153]]",0.04490177736202058,0.22972972972972974,0.16381418092909536
conloss_impute_corr_LightGBM,conloss,impute,corr,LightGBM,0.21054268309709537,0.1300563382162916,0.07801975274385842,1.0,0.12876935615321924,"[[0, 1069], [0, 158]]",0.0,0.22815884476534296,0.12876935615321924
conloss_impute_all_AdaBoost,conloss,impute,all,AdaBoost,0.19122785498322162,0.1292274359717539,0.16221623091726856,0.8037974683544303,0.12674650698602793,"[[194, 875], [31, 127]]",0.1814780168381665,0.2189655172413793,0.2616136919315403
conloss_impute_all_LightGBM,conloss,impute,all,LightGBM,0.20748179522070567,0.12911705586371353,0.07720331008273537,1.0,0.12876935615321924,"[[0, 1069], [0, 158]]",0.0,0.22815884476534296,0.12876935615321924
conloss_impute_corr_XGBoost,conloss,impute,corr,XGBoost,0.21824966351127129,0.12745308222418245,0.07830634713172913,1.0,0.12876935615321924,"[[0, 1069], [0, 158]]",0.0,0.22815884476534296,0.12876935615321924
conloss_impute_corr_AdaBoost,conloss,impute,corr,AdaBoost,0.1855365946860566,0.12527718659682988,0.18046469323195244,0.8481012658227848,0.12786259541984732,"[[155, 914], [24, 134]]",0.1449953227315248,0.2222222222222222,0.23553382233088835
negeq_keep_all_RandomForest,negeq,keep,all,RandomForest,0.4171470873591485,0.4674422502757218,0.059480461812911195,0.8529411764705882,0.16666666666666666,"[[1609, 145], [5, 29]]",0.9173318129988598,0.27884615384615385,0.9161073825503355
negeq_keep_corr_RandomForest,negeq,keep,corr,RandomForest,0.4463202572195174,0.4581926619607342,0.05046650879984213,0.8529411764705882,0.1228813559322034,"[[1547, 207], [5, 29]]",0.8819840364880274,0.21481481481481482,0.8814317673378076
negeq_keep_all_CatBoost,negeq,keep,all,CatBoost,0.5297020015536803,0.4040386068967264,0.012688657018005924,0.8235294117647058,0.14213197969543148,"[[1585, 169], [6, 28]]",0.9036488027366021,0.24242424242424243,0.9021252796420581
negeq_keep_corr_CatBoost,negeq,keep,corr,CatBoost,0.49866201483402844,0.3816117551233172,0.007393605695564846,0.7941176470588235,0.12053571428571429,"[[1557, 197], [7, 27]]",0.8876852907639681,0.20930232558139536,0.8859060402684564
negeq_keep_all_LightGBM,negeq,keep,all,LightGBM,0.5141002064384389,0.30394234176699225,0.0008399380078398809,0.7352941176470589,0.058004640371229696,"[[1348, 406], [9, 25]]",0.7685290763968073,0.10752688172043011,0.7678970917225951
negeq_keep_corr_LightGBM,negeq,keep,corr,LightGBM,0.49592921187427663,0.29992591793856643,0.00013188201136054325,0.7647058823529411,0.05295315682281059,"[[1289, 465], [8, 26]]",0.7348916761687572,0.09904761904761905,0.7354586129753915
negeq_keep_corr_XGBoost,negeq,keep,corr,XGBoost,0.4287427051530059,0.2694523037878131,0.01108479779213667,0.7058823529411765,0.11428571428571428,"[[1568, 186], [10, 24]]",0.8939566704675028,0.19672131147540983,0.8903803131991052
negeq_keep_all_XGBoost,negeq,keep,all,XGBoost,0.42674578858055773,0.26519646712233774,0.011342362500727177,0.6764705882352942,0.11386138613861387,"[[1575, 179], [11, 23]]",0.8979475484606614,0.19491525423728814,0.8937360178970917
negeq_impute_all_AdaBoost,negeq,impute,all,AdaBoost,0.09006153985655257,0.026135443349151966,0.3452967440652882,0.4411764705882353,0.01800720288115246,"[[936, 818], [19, 15]]",0.5336374002280502,0.03460207612456748,0.5318791946308725
negeq_impute_pca_AdaBoost,negeq,impute,pca,AdaBoost,0.08474752808297803,0.025583227790239964,0.24982568084045162,0.0,0.0,"[[1753, 1], [34, 0]]",0.999429874572406,0.0,0.9804250559284117
negeq_impute_pca_XGBoost,negeq,impute,pca,XGBoost,0.08199934661052383,0.020722971300160164,0.008357434533536434,0.9705882352941176,0.019549763033175356,"[[99, 1655], [1, 33]]",0.056442417331812995,0.03832752613240418,0.0738255033557047
negeq_impute_pca_LightGBM,negeq,impute,pca,LightGBM,0.09466597699153358,0.020156313564060845,0.007491902745421547,1.0,0.01901565995525727,"[[0, 1754], [0, 34]]",0.0,0.03732162458836443,0.01901565995525727
negeq_impute_corr_AdaBoost,negeq,impute,corr,AdaBoost,0.0845433379733001,0.019999861145556137,0.39055291556829064,0.7647058823529411,0.01915991156963891,"[[423, 1331], [8, 26]]",0.2411630558722919,0.037383177570093455,0.2511185682326622
negeq_impute_corr_CatBoost,negeq,impute,corr,CatBoost,0.09825068374924294,0.01824076491843875,0.11106818494619851,1.0,0.01901565995525727,"[[0, 1754], [0, 34]]",0.0,0.03732162458836443,0.01901565995525727
negeq_impute_pca_CatBoost,negeq,impute,pca,CatBoost,0.10885720738286374,0.018041963090630178,0.027811120171266263,0.9705882352941176,0.01905311778290993,"[[55, 1699], [1, 33]]",0.03135689851767389,0.03737259343148358,0.049217002237136466
negeq_impute_pca_RandomForest,negeq,impute,pca,RandomForest,0.11967706515387207,0.01796070089458172,0.009023368159728705,1.0,0.01901565995525727,"[[0, 1754], [0, 34]]",0.0,0.03732162458836443,0.01901565995525727
negeq_impute_all_LightGBM,negeq,impute,all,LightGBM,0.08986413743270867,0.017722029444499264,2.6023672887597216e-05,0.6764705882352942,0.018638573743922204,"[[543, 1211], [11, 23]]",0.30957810718358036,0.03627760252365931,0.31655480984340045
negeq_impute_all_XGBoost,negeq,impute,all,XGBoost,0.11007164005631398,0.017493939834249878,0.008673484437167645,1.0,0.019079685746352413,"[[6, 1748], [0, 34]]",0.0034207525655644243,0.037444933920704845,0.02237136465324385
negeq_impute_corr_XGBoost,negeq,impute,corr,XGBoost,0.1066687061960185,0.01714568745360183,0.00426494050770998,1.0,0.019551466359977,"[[49, 1705], [0, 34]]",0.027936145952109463,0.03835307388606881,0.046420581655480984
negeq_impute_all_CatBoost,negeq,impute,all,CatBoost,0.09700003338965547,0.016888990127705138,0.020205113489548995,1.0,0.01951779563719862,"[[46, 1708], [0, 34]]",0.026225769669327253,0.038288288288288286,0.0447427293064877
negeq_impute_corr_LightGBM,negeq,impute,corr,LightGBM,0.1077721254433791,0.016013899660255144,4.513603303341643e-07,0.6470588235294118,0.017027863777089782,"[[484, 1270], [12, 22]]",0.2759407069555302,0.033182503770739065,0.2829977628635347
negeq_impute_corr_RandomForest,negeq,impute,corr,RandomForest,0.10901927863599276,0.014907448215334046,0.008418424698410443,0.9705882352941176,0.018570624648283626,"[[10, 1744], [1, 33]]",0.005701254275940707,0.036443953616786304,0.024049217002237135
negeq_impute_all_RandomForest,negeq,impute,all,RandomForest,0.11048189870483469,0.014780235261005113,0.008311750428088816,1.0,0.01904761904761905,"[[3, 1751], [0, 34]]",0.0017103762827822121,0.037383177570093455,0.02069351230425056
ppk_keep_all_RandomForest,ppk,keep,all,RandomForest,0.5009549492844173,0.7543469006969028,0.1026076616985708,0.8640776699029126,0.5042492917847026,"[[270, 175], [28, 178]]",0.6067415730337079,0.6368515205724508,0.6881720430107527
ppk_keep_corr_CatBoost,ppk,keep,corr,CatBoost,0.5718798405170432,0.7477682949046598,0.009480744680310961,0.883495145631068,0.49056603773584906,"[[256, 189], [24, 182]]",0.5752808988764045,0.6308492201039861,0.6728110599078341
ppk_keep_corr_RandomForest,ppk,keep,corr,RandomForest,0.5160614227882753,0.7448321467620256,0.09740742823844975,0.8737864077669902,0.5202312138728323,"[[279, 166], [26, 180]]",0.6269662921348315,0.6521739130434783,0.7050691244239631
ppk_keep_all_CatBoost,ppk,keep,all,CatBoost,0.5745031086299868,0.7397997241048739,0.01813688630925825,0.8980582524271845,0.4567901234567901,"[[225, 220], [21, 185]]",0.5056179775280899,0.6055646481178396,0.6298003072196621
ppk_keep_all_XGBoost,ppk,keep,all,XGBoost,0.548289360120827,0.7166120551596885,0.015902331098914146,0.8980582524271845,0.46954314720812185,"[[236, 209], [21, 185]]",0.5303370786516854,0.6166666666666667,0.6466973886328725
ppk_keep_corr_LightGBM,ppk,keep,corr,LightGBM,0.556768921077627,0.7102702588981185,0.008526471325700261,0.941747572815534,0.42543859649122806,"[[183, 262], [12, 194]]",0.41123595505617977,0.5861027190332326,0.5791090629800307
ppk_keep_corr_XGBoost,ppk,keep,corr,XGBoost,0.5422394181887022,0.7065437167917236,0.03040345199406147,0.8495145631067961,0.5131964809384164,"[[279, 166], [31, 175]]",0.6269662921348315,0.6398537477148081,0.6973886328725039
ppk_keep_all_LightGBM,ppk,keep,all,LightGBM,0.5620967724601429,0.7050192204622268,0.0025382668280570612,0.8980582524271845,0.417607223476298,"[[187, 258], [21, 185]]",0.4202247191011236,0.5701078582434514,0.5714285714285714
ppk_impute_all_RandomForest,ppk,impute,all,RandomForest,0.36976494064866383,0.3640249704273563,0.07135664391305577,0.9611650485436893,0.31629392971246006,"[[17, 428], [8, 198]]",0.038202247191011236,0.47596153846153844,0.3302611367127496
ppk_impute_corr_RandomForest,ppk,impute,corr,RandomForest,0.3433272032810414,0.34920716385216366,0.06876373959258938,1.0,0.31643625192012287,"[[0, 445], [0, 206]]",0.0,0.4807467911318553,0.31643625192012287
ppk_impute_pca_RandomForest,ppk,impute,pca,RandomForest,0.33971520610202244,0.3452447976393893,0.11050689486787096,0.5631067961165048,0.3240223463687151,"[[203, 242], [90, 116]]",0.45617977528089887,0.41134751773049644,0.49001536098310294
ppk_impute_pca_XGBoost,ppk,impute,pca,XGBoost,0.3782298655516516,0.33117039734293896,0.06325820833444595,0.6553398058252428,0.3276699029126214,"[[168, 277], [71, 135]]",0.3775280898876405,0.4368932038834951,0.46543778801843316
ppk_impute_pca_AdaBoost,ppk,impute,pca,AdaBoost,0.3101419855232404,0.3302806030032098,0.4295207244500669,0.23300970873786409,0.31788079470198677,"[[342, 103], [158, 48]]",0.7685393258426966,0.2689075630252101,0.5990783410138248
ppk_impute_pca_CatBoost,ppk,impute,pca,CatBoost,0.39159352562580385,0.3297741572053883,0.036930656688580636,0.7135922330097088,0.3075313807531381,"[[114, 331], [59, 147]]",0.25617977528089886,0.4298245614035088,0.4009216589861751
ppk_impute_pca_LightGBM,ppk,impute,pca,LightGBM,0.3838678068846172,0.3204096798568804,0.001057382472860725,0.7572815533980582,0.31771894093686354,"[[110, 335], [50, 156]]",0.24719101123595505,0.44763271162123386,0.40860215053763443
ppk_impute_corr_XGBoost,ppk,impute,corr,XGBoost,0.3730458741905497,0.3125408709685088,0.07499814033508301,1.0,0.31643625192012287,"[[0, 445], [0, 206]]",0.0,0.4807467911318553,0.31643625192012287
ppk_impute_all_XGBoost,ppk,impute,all,XGBoost,0.38188111933357916,0.3039408264470023,0.012296569533646107,0.9951456310679612,0.31587057010785824,"[[1, 444], [1, 205]]",0.0022471910112359553,0.47953216374269003,0.31643625192012287
ppk_impute_corr_LightGBM,ppk,impute,corr,LightGBM,0.38290476992270606,0.3038816889339783,0.004446654234049561,0.9951456310679612,0.3153846153846154,"[[0, 445], [1, 205]]",0.0,0.47897196261682246,0.31490015360983103
ppk_impute_all_LightGBM,ppk,impute,all,LightGBM,0.4013469597133256,0.3033984859011807,3.415264436267724e-05,0.8495145631067961,0.3038194444444444,"[[44, 401], [31, 175]]",0.09887640449438202,0.4475703324808184,0.33640552995391704
ppk_impute_corr_CatBoost,ppk,impute,corr,CatBoost,0.40764813662598715,0.30157573733219123,0.00401453094057985,0.9757281553398058,0.31703470031545744,"[[12, 433], [5, 201]]",0.02696629213483146,0.4785714285714286,0.3271889400921659
ppk_impute_corr_AdaBoost,ppk,impute,corr,AdaBoost,0.3209833269534381,0.29544979984519526,0.2706962006722341,0.6310679611650486,0.32338308457711445,"[[173, 272], [76, 130]]",0.3887640449438202,0.4276315789473684,0.46543778801843316
ppk_impute_all_CatBoost,ppk,impute,all,CatBoost,0.40995220407316535,0.2945449162873124,0.004135391656362598,0.9854368932038835,0.31521739130434784,"[[4, 441], [3, 203]]",0.008988764044943821,0.4776470588235294,0.31797235023041476
ppk_impute_all_AdaBoost,ppk,impute,all,AdaBoost,0.3105474759887357,0.28285294542946227,0.35292381769287745,0.23300970873786409,0.24742268041237114,"[[299, 146], [158, 48]]",0.6719101123595506,0.24,0.533026113671275

Performa vs Baseline

# 1. Persiapan Data
indikator = ['Papan Pemantauan\nKhusus', 'Ekuitas\nNegatif', 'Rugi\nBerturut-turut']
baseline = [31.6, 1.9, 12.9]
performa = [75.4, 46.7, 62.5]

# Menentukan lokasi koordinat sumbu X
x = np.arange(len(indikator))

# 2. Inisialisasi Kanvas Plot
fig, ax = plt.subplots(figsize=(10, 7))

# 3. Pembuatan Bar Overlay (Tumpang Tindih)
ax.bar(x, baseline, width=0.6, color='gray', alpha=0.4, label='Baseline PR-AUC')
ax.bar(x, performa, width=0.3, color='seagreen', alpha=0.9, label='Performa Aktual')

# 4. Anotasi Angka Persentase & Multiplier
for i in range(len(x)):
    # Membagi performa aktual dengan baseline untuk mencari tahu berapa kali lipat kenaikannya
    kelipatan = performa[i] / baseline[i]

    # Mencetak persentase baseline
    ax.text(x[i] - 0.32, baseline[i] + 0.5, f"{baseline[i]}%", 
            ha='right', va='bottom', fontsize=11, fontweight='bold', color='dimgray')

    # Mencetak persentase performa aktual 
    ax.text(x[i], performa[i] + 1.5, f"{performa[i]}%", 
            ha='center', va='bottom', fontsize=12, fontweight='bold', color='darkgreen')
    
    # --- PERBAIKAN UKURAN BADGE KELIPATAN ---
    # 1. Jarak vertikal dinaikkan (+ 8.0) agar tidak menabrak teks persentase
    # 2. fontsize dinaikkan dari 10 menjadi 14
    # 3. pad (bantalan kotak) ditebalkan dari 0.3 menjadi 0.6
    ax.text(x[i], performa[i] + 8.0, f" {kelipatan:.1f}x Lipat ", 
            ha='center', va='bottom', fontsize=14, fontweight='bold', color='white',
            bbox=dict(facecolor='darkorange', edgecolor='none', boxstyle='round,pad=0.6', alpha=0.9))

# 5. Konfigurasi Kosmetik Sumbu dan Judul
ax.set_ylabel('Skor PR-AUC (%)', fontsize=14, fontweight='bold', labelpad=10)
ax.set_title('Perbandingan Performa Model terhadap Baseline', fontsize=18, fontweight='bold', pad=20)

ax.set_xticks(x)
ax.set_xticklabels(indikator, fontsize=12, fontweight='bold')

# --- PERBAIKAN LIMIT SUMBU Y ---
# Batas atas dinaikkan menjadi 110 agar kotak kelipatan yang sekarang menjadi raksasa tidak terpotong
ax.set_ylim(0, 110)

ax.grid(axis='y', linestyle='--', alpha=0.4)

# Menempatkan kotak legenda di pojok kanan atas
ax.legend(loc='upper right', fontsize=12, framealpha=0.9)

# Merapikan tata letak
plt.tight_layout()
plt.show()

Feature Importance

FI Target Distress Papan Pemantauan Khusus

ppk_best_configs = ['ppk_keep_all_RandomForest','ppk_keep_corr_CatBoost']
# [FIX] Sembunyikan warning tight_layout (tidak berbahaya, hanya informasi)
warnings.filterwarnings("ignore", message="This figure includes Axes that are not compatible with tight_layout")

# [FIX] Set DPI tinggi & font dasar lebih besar
plt.rcParams['figure.dpi'] = 150
plt.rcParams['savefig.dpi'] = 300
plt.rcParams['font.size'] = 16

n_rows = len(ppk_best_configs)
n_cols = 3

# [FIX] Kanvas diperlebar agar plot kanan punya ruang untuk digeser ke kanan
fig, axes = plt.subplots(nrows=n_rows, ncols=n_cols, figsize=(33, 8 * n_rows),
                         gridspec_kw={'width_ratios': [1.3, 1.55, 1.05], 'wspace': 0.18})

fig.suptitle("Pengaruh dan Kontribusi Fitur\nIndikator Papan Pemantauan Khusus",
              fontsize=26, fontweight='bold', y=0.97)

axes = np.atleast_2d(axes)

for i, chosen_id in enumerate(ppk_best_configs):

    ax_shap = axes[i, 0]
    ax_spearman = axes[i, 1]
    ax_imp = axes[i, 2]

    chosen_model = dict_fitted_models[chosen_id]
    X_test_clean_chosen = dict_detailed_predictions[chosen_id]['X_test_features'].copy()
    X_test_clean_chosen = X_test_clean_chosen.fillna(np.nan).astype(float)

    explainer = shap.TreeExplainer(chosen_model)
    shap_values = explainer.shap_values(X_test_clean_chosen)

    if isinstance(shap_values, list):
        shap_values_plot = shap_values[1]
    elif len(shap_values.shape) == 3:
        shap_values_plot = shap_values[:, :, 1]
    else:
        shap_values_plot = shap_values

    mean_abs_shap = np.abs(shap_values_plot).mean(axis=0)
    df_shap_order = pd.DataFrame({'Feature': X_test_clean_chosen.columns, 'Mean_Abs_SHAP': mean_abs_shap})
    top_15_features_shap = df_shap_order.sort_values(by='Mean_Abs_SHAP', ascending=False)['Feature'].head(15).tolist()

    # ----------------------------------------------------------
    # KOLOM 1: SHAP PLOT
    # ----------------------------------------------------------
    plt.sca(ax_shap)

    shap.summary_plot(shap_values_plot, X_test_clean_chosen, max_display=15,
                      show=False, plot_size=None)

    ax_shap.set_title("SHAP Values Summary", fontsize=20, fontweight='bold', pad=15)

    if "RandomForest" in chosen_model.__class__.__name__:
        ax_shap.set_xlabel("SHAP value (Impact on Probability)", fontsize=18)
    else:
        ax_shap.set_xlabel("SHAP value (Impact on Log-Odds)", fontsize=18)

    ax_shap.tick_params(axis='y', labelsize=18)
    ax_shap.tick_params(axis='x', labelsize=16)

    for fig_ax in fig.axes:
        if fig_ax.get_ylabel() == "Feature value":
            fig_ax.set_ylabel("Feature value", fontsize=17)
            fig_ax.tick_params(labelsize=16)

    ax_shap.annotate(chosen_id,
                     xy=(-0.85, 0.5), xycoords='axes fraction',
                     rotation=90, va='center', ha='center',
                     fontsize=20, fontweight='bold', color='black',
                     clip_on=False)

    # ----------------------------------------------------------
    # KOLOM 2: ARAH KORELASI SPEARMAN
    # ----------------------------------------------------------
    spearman_rows = []
    for j, col in enumerate(X_test_clean_chosen.columns):
        feature_vals = X_test_clean_chosen.iloc[:, j]
        shap_vals = pd.Series(shap_values_plot[:, j], index=feature_vals.index)

        if feature_vals.nunique() > 1 and shap_vals.nunique() > 1:
            corr = feature_vals.corr(shap_vals, method='spearman')
            corr = 0.0 if pd.isna(corr) else corr
        else:
            corr = 0.0

        spearman_rows.append({'Feature': col, 'Spearman_Corr': corr})

    df_spearman = pd.DataFrame(spearman_rows)
    df_spearman = df_spearman.set_index('Feature').loc[top_15_features_shap].reset_index()

    colors = ['#c62828' if val > 0 else '#2e7d32' for val in df_spearman['Spearman_Corr']]

    sns.barplot(x='Spearman_Corr', y='Feature', data=df_spearman,
                hue='Feature', palette=colors, legend=False, ax=ax_spearman)

    ax_spearman.axvline(0, color='black', linewidth=1.2)
    ax_spearman.set_title("Arah Pengaruh (Spearman Correlation)", fontsize=20, fontweight='bold', pad=15)
    ax_spearman.set_ylabel('')
    ax_spearman.set_yticks([])
    ax_spearman.set_xlabel("Spearman r (Positif = Memperparah Distress)", fontsize=18)
    ax_spearman.tick_params(axis='x', labelsize=16)
    ax_spearman.locator_params(axis='x', nbins=8)

    max_abs_corr = df_spearman['Spearman_Corr'].abs().max()
    xlim_val = max(max_abs_corr * 1.15, 0.1)
    ax_spearman.set_xlim(-xlim_val, xlim_val)
    ax_spearman.margins(x=0)

    # ----------------------------------------------------------
    # KOLOM 3: FEATURE IMPORTANCE
    # ----------------------------------------------------------
    importance = chosen_model.feature_importances_
    df_imp = pd.DataFrame({'Feature': X_test_clean_chosen.columns, 'Importance': importance})
    df_imp = df_imp.sort_values(by='Importance', ascending=False).head(15)

    sns.barplot(x='Importance', y='Feature', data=df_imp, hue='Feature',
                palette='viridis', legend=False, ax=ax_imp)

    ax_imp.set_title("Top 15 Feature Importance (Model)", fontsize=20, fontweight='bold', pad=15)
    ax_imp.set_ylabel('')
    ax_imp.set_xlabel("Importance", fontsize=18)
    ax_imp.tick_params(axis='y', labelsize=18)
    ax_imp.tick_params(axis='x', labelsize=16)

# ==========================================================
# PENYESUAIAN MARGIN (kembali ke tight_layout seperti sebelumnya)
# ==========================================================
plt.tight_layout(rect=[0.08, 0, 1, 0.93])

# [FIX] Geser hanya plot kanan (Feature Importance) lebih ke kanan,
# tanpa mengubah posisi plot SHAP & Spearman
fig.canvas.draw()

SHIFT = 0.035  # seberapa jauh plot kanan digeser ke kanan (dalam koordinat figure 0-1)

for i in range(n_rows):
    pos_imp = axes[i, 2].get_position()
    new_pos_imp = [pos_imp.x0 + SHIFT, pos_imp.y0, pos_imp.width, pos_imp.height]
    axes[i, 2].set_position(new_pos_imp)

# [FIX] Gambar garis pembatas vertikal di celah antara Spearman & Feature Importance
# (posisi dihitung ulang setelah pergeseran di atas)
for i in range(n_rows):
    bbox_spearman = axes[i, 1].get_position()
    bbox_imp = axes[i, 2].get_position()

    x_sep = (bbox_spearman.x1 + bbox_imp.x0) / 2

    y0 = bbox_imp.y0 - 0.01
    y1 = bbox_imp.y1 + 0.01

    line = Line2D([x_sep, x_sep], [y0, y1], transform=fig.transFigure,
                   color='gray', linestyle='--', linewidth=1.5, alpha=0.7)
    line.set_clip_on(False)
    fig.add_artist(line)

plt.savefig("ppk_shap_summary.png", dpi=300, bbox_inches='tight')
plt.show()

for chosen_id in ppk_best_configs:

    chosen_model = dict_fitted_models[chosen_id]
    X_test_clean_chosen = dict_detailed_predictions[chosen_id]['X_test_features'].copy()
    X_test_clean_chosen = X_test_clean_chosen.fillna(np.nan).astype(float)

    explainer = shap.TreeExplainer(chosen_model)
    shap_values = explainer.shap_values(X_test_clean_chosen)

    if isinstance(shap_values, list):
        shap_values_plot = shap_values[1]
    elif len(shap_values.shape) == 3:
        shap_values_plot = shap_values[:, :, 1]
    else:
        shap_values_plot = shap_values

    # ----------------------------------------------------------
    # DATAFRAME 1: Mean |SHAP| (Top 15) + Arah Pengaruh (Spearman)
    # ----------------------------------------------------------
    mean_abs_shap = np.abs(shap_values_plot).mean(axis=0)
    df_shap_order = pd.DataFrame({
        'Feature': X_test_clean_chosen.columns,
        'Mean_Abs_SHAP': mean_abs_shap
    })

    top_15_shap = df_shap_order.sort_values(by='Mean_Abs_SHAP', ascending=False).head(15).reset_index(drop=True)

    spearman_rows = []
    for feat in top_15_shap['Feature']:
        j = list(X_test_clean_chosen.columns).index(feat)
        feature_vals = X_test_clean_chosen.iloc[:, j]
        shap_vals = pd.Series(shap_values_plot[:, j], index=feature_vals.index)

        if feature_vals.nunique() > 1 and shap_vals.nunique() > 1:
            corr = feature_vals.corr(shap_vals, method='spearman')
            corr = 0.0 if pd.isna(corr) else corr
        else:
            corr = 0.0

        spearman_rows.append(corr)

    top_15_shap['Spearman_Corr'] = spearman_rows
    top_15_shap['Arah_Pengaruh'] = top_15_shap['Spearman_Corr'].apply(
        lambda x: 'Positif (Memperparah Distress)' if x > 0
        else ('Negatif (Mengurangi Distress)' if x < 0 else 'Netral')
    )

    df1 = top_15_shap[['Feature', 'Mean_Abs_SHAP', 'Spearman_Corr', 'Arah_Pengaruh']]

    # ----------------------------------------------------------
    # DATAFRAME 2: Feature Importance Model (Top 15)
    # ----------------------------------------------------------
    importance = chosen_model.feature_importances_
    df_imp = pd.DataFrame({
        'Feature': X_test_clean_chosen.columns,
        'Importance': importance
    })

    df2 = df_imp.sort_values(by='Importance', ascending=False).head(15).reset_index(drop=True)

    # ----------------------------------------------------------
    # CETAK DALAM FORMAT CSV
    # ----------------------------------------------------------
    print(f"=== {chosen_id} - SHAP & Arah Pengaruh (Top 15) ===")
    print(df1.to_csv(index=False))

    print(f"=== {chosen_id} - Feature Importance (Top 15) ===")
    print(df2.to_csv(index=False))
=== ppk_keep_all_RandomForest - SHAP & Arah Pengaruh (Top 15) ===
Feature,Mean_Abs_SHAP,Spearman_Corr,Arah_Pengaruh
Sales_per_share,0.018086309141178362,-0.38384339191384514,Negatif (Mengurangi Distress)
log_MktCap,0.014268786710354983,-0.8890226335896925,Negatif (Mengurangi Distress)
EPS_proxy,0.013631205671255605,-0.637823597340716,Negatif (Mengurangi Distress)
log_TA,0.011865081811796682,-0.9071374420157321,Negatif (Mengurangi Distress)
CashST_TA,0.009411639257299135,-0.6333483101106295,Negatif (Mengurangi Distress)
log_Sales,0.008419484779264423,-0.8726748335772678,Negatif (Mengurangi Distress)
Parent Percent Owned (%),0.008161288728794585,0.3050614663579201,Positif (Memperparah Distress)
Sales_growth,0.007634299295551004,0.894366490572696,Positif (Memperparah Distress)
ROE,0.006183140409879697,-0.6037439188557336,Negatif (Mengurangi Distress)
NetMargin,0.006055307454870257,-0.8059455486132707,Negatif (Mengurangi Distress)
CashST_CL,0.0059698796557021965,-0.6291640479053897,Negatif (Mengurangi Distress)
Debt_Equity,0.005013153922554357,0.008124215965337342,Positif (Memperparah Distress)
CFO_per_share,0.004481925279623213,-0.686046152541304,Negatif (Mengurangi Distress)
CR,0.0044782199144499865,-0.826807235742521,Negatif (Mengurangi Distress)
ROA,0.004445465228575167,-0.57771090180352,Negatif (Mengurangi Distress)

=== ppk_keep_all_RandomForest - Feature Importance (Top 15) ===
Feature,Importance
Sales_per_share,0.03331554881196363
Sales_growth,0.03190100747862269
EPS_proxy,0.028094634551687953
CashST_TA,0.02580235904107479
log_MktCap,0.024996616645981375
NetDebt_EBITDA,0.024289307273134025
log_TA,0.024170724940866587
log_Sales,0.024168798593961992
Inventory_CA,0.02416842954461248
Prepaid_CA,0.02401141857529439
Parent Percent Owned (%),0.023334035986800297
CashST_CL,0.022305652759122403
NI_growth,0.022192215843909253
Debt_Equity,0.022071828941036584
Altman_X3_EBIT_TA,0.02184114394584633

=== ppk_keep_corr_CatBoost - SHAP & Arah Pengaruh (Top 15) ===
Feature,Mean_Abs_SHAP,Spearman_Corr,Arah_Pengaruh
Sales_per_share,0.4568988577297765,-0.6640751332577971,Negatif (Mengurangi Distress)
Sales_growth,0.3435495824134642,0.9068938597472925,Positif (Memperparah Distress)
Parent Percent Owned (%),0.2752592854519673,0.07771335190990666,Positif (Memperparah Distress)
log_Sales,0.21420615900594095,-0.8775068232776526,Negatif (Mengurangi Distress)
log_TA,0.21169342712398878,-0.8484704331884225,Negatif (Mengurangi Distress)
log_MktCap,0.18661485481868267,-0.9007935977625477,Negatif (Mengurangi Distress)
Years_Since_IPO,0.17945489253947436,0.7924165837498359,Positif (Memperparah Distress)
CashST_TA,0.14352596380794094,-0.7954335472562268,Negatif (Mengurangi Distress)
EPS_proxy,0.11901717782178424,-0.7986215130604603,Negatif (Mengurangi Distress)
Debt_Equity,0.11514953297867045,-0.40588294125013075,Negatif (Mengurangi Distress)
PPE_TA,0.10965199304196346,-0.08709612967677485,Negatif (Mengurangi Distress)
OpMargin,0.10647990876797471,-0.7224074045882872,Negatif (Mengurangi Distress)
Percent Owned - All Institutions (%),0.10060927808983898,-0.5341527830399403,Negatif (Mengurangi Distress)
Intang_TA,0.09737482079305851,-0.6781619865398005,Negatif (Mengurangi Distress)
GrossMargin,0.09484705214836112,-0.47020386097134037,Negatif (Mengurangi Distress)

=== ppk_keep_corr_CatBoost - Feature Importance (Top 15) ===
Feature,Importance
Sales_growth,5.395527823659915
Sales_per_share,4.6003206272365835
Parent Percent Owned (%),4.344947203390322
Prepaid_CA,3.4537502212256275
GrossMargin,3.208472588903182
log_Sales,3.1071012551659005
log_TA,2.9234321016382596
Inventory_CA,2.8022457339167346
log_MktCap,2.7227591028485145
PB,2.674316475030797
CA_TA,2.580351402955231
PPE_TA,2.5539363343931605
TA_growth,2.4902256663287323
NetDebt_EBITDA,2.4855775773300057
NI_growth,2.4672570524756376
for chosen_id in ppk_best_configs:
    print(f"\n========== MENGANALISIS: {chosen_id} ==========")

    chosen_model = dict_fitted_models[chosen_id]
    X_test_clean_chosen = dict_detailed_predictions[chosen_id]['X_test_features'].copy()
    X_test_clean_chosen = X_test_clean_chosen.fillna(np.nan).astype(float)

    # ---------- SHAP ----------
    explainer = shap.TreeExplainer(chosen_model)
    shap_values = explainer.shap_values(X_test_clean_chosen)
    if isinstance(shap_values, list):
        shap_values_plot = shap_values[1]
    elif len(shap_values.shape) == 3:
        shap_values_plot = shap_values[:, :, 1]
    else:
        shap_values_plot = shap_values

    feature_names = list(X_test_clean_chosen.columns)
    mean_abs_shap = np.abs(shap_values_plot).mean(axis=0)
    tree_importance = chosen_model.feature_importances_

    industry_idx = [i for i, c in enumerate(feature_names) if c.startswith('industry_')]
    other_idx = [i for i, c in enumerate(feature_names) if not c.startswith('industry_')]

    rows = []
    for i in other_idx:
        feature_vals = X_test_clean_chosen.iloc[:, i]
        shap_vals = pd.Series(shap_values_plot[:, i], index=feature_vals.index)
        rows.append({
            'Feature': feature_names[i],
            'Mean_Abs_SHAP': mean_abs_shap[i],
            'Tree_Importance': tree_importance[i],
            'SHAP_Dir_Spearman': feature_vals.corr(shap_vals, method='spearman')
        })

    # ---------- Agregasi Industry Group ----------
    if industry_idx:
        industry_shap_rowsum = shap_values_plot[:, industry_idx].sum(axis=1)
        rows.append({
            'Feature': 'Industry Group (agregat)',
            'Mean_Abs_SHAP': np.abs(industry_shap_rowsum).mean(),
            'Tree_Importance': tree_importance[industry_idx].sum(),
            'SHAP_Dir_Spearman': np.nan
        })

    df_importance = pd.DataFrame(rows).sort_values(
        by='Mean_Abs_SHAP', ascending=False).reset_index(drop=True)

    print(df_importance.to_csv(index=False))

========== MENGANALISIS: ppk_keep_all_RandomForest ==========
Feature,Mean_Abs_SHAP,Tree_Importance,SHAP_Dir_Spearman
Sales_per_share,0.018086309141178362,0.03331554881196363,-0.38384339191384514
log_MktCap,0.014268786710354983,0.024996616645981375,-0.8890226335896925
EPS_proxy,0.013631205671255605,0.028094634551687953,-0.637823597340716
log_TA,0.011865081811796682,0.024170724940866587,-0.9071374420157321
CashST_TA,0.009411639257299135,0.02580235904107479,-0.6333483101106295
log_Sales,0.008419484779264423,0.024168798593961992,-0.8726748335772678
Parent Percent Owned (%),0.008161288728794585,0.023334035986800297,0.3050614663579201
Sales_growth,0.007634299295551004,0.03190100747862269,0.894366490572696
ROE,0.006183140409879697,0.021834770589707484,-0.6037439188557336
NetMargin,0.006055307454870257,0.020272541730230628,-0.8059455486132707
CashST_CL,0.0059698796557021965,0.022305652759122403,-0.6291640479053897
Debt_Equity,0.005013153922554357,0.022071828941036584,0.008124215965337342
CFO_per_share,0.004481925279623213,0.02025783575642112,-0.686046152541304
CR,0.0044782199144499865,0.01978030876919815,-0.826807235742521
ROA,0.004445465228575167,0.0203004909662637,-0.57771090180352
Prepaid_CA,0.004360598822409726,0.02401141857529439,-0.4096175945284868
Debt_TA,0.004283306935427938,0.018876955776698765,0.1667954846529983
Years_Since_IPO,0.004099501598835011,0.016470873918940337,0.6303940449967198
NetDebt_EBITDA,0.0037926879341788106,0.024289307273134025,0.5638168069545686
PB,0.0037242212592953528,0.020715212069070434,0.17774643721605923
OpMargin,0.0037213597320908567,0.018455874048958626,-0.5644633696009117
GrossMargin,0.0035763292533206865,0.02010577506047018,-0.6477292119853809
Age_When_IPO,0.0035344789231686923,0.01672962281638334,-0.5691350414272497
TL_TA,0.00347276659835204,0.019608628123669383,-0.2960872770609561
Inventory_CA,0.0034613671950798224,0.02416842954461248,0.07788302210892036
CFO_growth,0.0033533593472055995,0.02008573642518434,-0.29041503221761444
Equity_TA,0.0033349828479915826,0.020661664497623307,0.10067973981292802
CA_TA,0.0032629989222235705,0.02079646346898236,0.013898739297512305
Percent Owned - All Institutions (%),0.003242532155906701,0.01580165475484876,-0.010643514236265177
QR,0.002984941083517711,0.019035510425341188,-0.6724598597370334
Altman_X3_EBIT_TA,0.002905041148237055,0.02184114394584633,-0.5385481753535514
Altman_X4_MVE_TL,0.002858715161499423,0.01977258034698885,-0.3273401287016961
EBITDA_TA,0.0027856843998968904,0.02123438306704055,-0.4203827446784283
Altman_X5_SalesTA_AssetTurnover,0.0027755997890706804,0.019458642501764024,0.3800515850664278
Industry Group (agregat),0.0027141401576190505,0.022460465722608555,
Intang_TA,0.0025330360810353865,0.012920873486184464,-0.42282266352816367
Equity_growth,0.0025024300318317117,0.01976764575012234,-0.4479080557449185
Percent Owned - Insiders (%),0.0024834921993119384,0.01889055293049523,-0.1045389413253252
TA_growth,0.0024377131249507114,0.021779129198522467,-0.24646389455479922
NI_growth,0.0023469232716228894,0.022192215843909253,-0.46206415160012326
Altman_X1_WC_TA,0.0021538497279937276,0.018059550691629133,-0.3145550353070638
NetCash_TA,0.002043258460940024,0.015984947847918818,-0.13271397907455695
WC_Sales,0.0020106927050525247,0.01631812381189096,-0.6556910811333927
CFO_Sales,0.0019363556360147552,0.017996715527145445,0.27753292027584525
CL_TA,0.0018686363310112494,0.01736705083019999,-0.4102551633998299
CFO_TA,0.0018658565386384966,0.016361184642392654,-0.07195429545716504
PPE_TA,0.00186404424341517,0.016652050584519008,0.46647360294161827
CFO_TL,0.0017623289929889557,0.018522460928670468,-0.07977408698564448


========== MENGANALISIS: ppk_keep_corr_CatBoost ==========
Feature,Mean_Abs_SHAP,Tree_Importance,SHAP_Dir_Spearman
Sales_per_share,0.4568988577297765,4.6003206272365835,-0.6640751332577971
Sales_growth,0.3435495824134642,5.395527823659915,0.9068938597472925
Parent Percent Owned (%),0.2752592854519673,4.344947203390322,0.07771335190990666
log_Sales,0.21420615900594095,3.1071012551659005,-0.8775068232776526
log_TA,0.21169342712398878,2.9234321016382596,-0.8484704331884225
log_MktCap,0.18661485481868267,2.7227591028485145,-0.9007935977625477
Years_Since_IPO,0.17945489253947436,2.298320240787829,0.7924165837498359
CashST_TA,0.14352596380794094,2.440960345494906,-0.7954335472562268
EPS_proxy,0.11901717782178424,2.18246145043081,-0.7986215130604603
Debt_Equity,0.11514953297867045,2.2812914278113943,-0.40588294125013075
PPE_TA,0.10965199304196346,2.5539363343931605,-0.08709612967677485
OpMargin,0.10647990876797471,1.6365973454534706,-0.7224074045882872
Percent Owned - All Institutions (%),0.10060927808983898,1.7484806025787807,-0.5341527830399403
Intang_TA,0.09737482079305851,1.8540711686019387,-0.6781619865398005
GrossMargin,0.09484705214836112,3.208472588903182,-0.47020386097134037
Age_When_IPO,0.08868723897191338,2.165030439718456,-0.8528185935257897
Percent Owned - Insiders (%),0.0876870644252517,1.9596661201469083,-0.7928384582179576
PB,0.0841211670656311,2.674316475030797,0.4977301700871857
Altman_X3_EBIT_TA,0.08395005271005772,1.903655415292499,-0.6864083103938363
Prepaid_CA,0.08334913309042867,3.4537502212256275,-0.2963204867093475
QR,0.08222387917903641,2.030750382901075,0.0017419766978667899
NetDebt_EBITDA,0.08101290532066484,2.4855775773300057,0.5322239834775373
CashST_CL,0.08057640367462898,2.1893680150746393,-0.771300478662442
Debt_TA,0.08009739936171516,2.0003435391782096,0.42486680646201064
NetMargin,0.07866334315337326,1.5619428309479266,-0.8440126309471253
CA_TA,0.07338747377541263,2.580351402955231,-0.4412437394388711
CFO_per_share,0.07282600168292785,1.8656567952246417,-0.6264755786465662
Inventory_CA,0.07244679400915192,2.8022457339167346,0.2511640658361944
CFO_growth,0.06957912236266213,2.353092377655193,-0.13416226451160917
Altman_X4_MVE_TL,0.06950280443989977,2.0038940881035585,-0.6562375957705465
WC_Sales,0.06654176505229885,1.5128700465348077,-0.7900350787150708
ROE,0.06389917087855973,2.061383292404402,-0.5419256105066516
CFO_TL,0.06374947087007647,1.96473060709826,-0.635302888285275
TA_growth,0.0636232803401504,2.4902256663287323,-0.2942924425735969
NI_growth,0.06036453479774485,2.4672570524756376,-0.6313136420726092
Equity_growth,0.05958541493863396,1.538132587234043,-0.4576320474641444
CR,0.05331244199674574,1.3413231651256114,-0.7965993915122525
CFO_Sales,0.05068120140885691,1.5047121513716637,0.5712648562599086
Equity_TA,0.05031493531020322,1.5965299147908134,-0.38511040117610473
Altman_X5_SalesTA_AssetTurnover,0.04825185115865816,2.125025286510304,0.3685367195638322
CL_TA,0.04099657263514009,1.8581461831147768,-0.09599289291749216
NetCash_TA,0.039211175576788465,1.4720644441323183,-0.11546930014375098
Industry Group (agregat),0.028782340898690677,0.7392785697821841,

FI Target Distress Ekuitas Negatif

negeq_best_configs = ['negeq_keep_all_RandomForest', 'negeq_keep_corr_RandomForest']
# [FIX] Sembunyikan warning tight_layout (tidak berbahaya, hanya informasi)
warnings.filterwarnings("ignore", message="This figure includes Axes that are not compatible with tight_layout")

# [FIX] Set DPI tinggi & font dasar lebih besar
plt.rcParams['figure.dpi'] = 150
plt.rcParams['savefig.dpi'] = 300
plt.rcParams['font.size'] = 16

n_rows = len(negeq_best_configs)
n_cols = 3

# [FIX] Kanvas diperlebar agar plot kanan punya ruang untuk digeser ke kanan
fig, axes = plt.subplots(nrows=n_rows, ncols=n_cols, figsize=(33, 8 * n_rows),
                         gridspec_kw={'width_ratios': [1.3, 1.55, 1.05], 'wspace': 0.18})

fig.suptitle("Pengaruh dan Kontribusi Fitur\nIndikator Ekuitas Negatif",
              fontsize=26, fontweight='bold', y=0.97)

axes = np.atleast_2d(axes)

for i, chosen_id in enumerate(negeq_best_configs):

    ax_shap = axes[i, 0]
    ax_spearman = axes[i, 1]
    ax_imp = axes[i, 2]

    chosen_model = dict_fitted_models[chosen_id]
    X_test_clean_chosen = dict_detailed_predictions[chosen_id]['X_test_features'].copy()
    X_test_clean_chosen = X_test_clean_chosen.fillna(np.nan).astype(float)

    explainer = shap.TreeExplainer(chosen_model)
    shap_values = explainer.shap_values(X_test_clean_chosen)

    if isinstance(shap_values, list):
        shap_values_plot = shap_values[1]
    elif len(shap_values.shape) == 3:
        shap_values_plot = shap_values[:, :, 1]
    else:
        shap_values_plot = shap_values

    mean_abs_shap = np.abs(shap_values_plot).mean(axis=0)
    df_shap_order = pd.DataFrame({'Feature': X_test_clean_chosen.columns, 'Mean_Abs_SHAP': mean_abs_shap})
    top_15_features_shap = df_shap_order.sort_values(by='Mean_Abs_SHAP', ascending=False)['Feature'].head(15).tolist()

    # ----------------------------------------------------------
    # KOLOM 1: SHAP PLOT
    # ----------------------------------------------------------
    plt.sca(ax_shap)

    shap.summary_plot(shap_values_plot, X_test_clean_chosen, max_display=15,
                      show=False, plot_size=None)

    ax_shap.set_title("SHAP Values Summary", fontsize=20, fontweight='bold', pad=15)

    if "RandomForest" in chosen_model.__class__.__name__:
        ax_shap.set_xlabel("SHAP value (Impact on Probability)", fontsize=18)
    else:
        ax_shap.set_xlabel("SHAP value (Impact on Log-Odds)", fontsize=18)

    ax_shap.tick_params(axis='y', labelsize=18)
    ax_shap.tick_params(axis='x', labelsize=16)

    for fig_ax in fig.axes:
        if fig_ax.get_ylabel() == "Feature value":
            fig_ax.set_ylabel("Feature value", fontsize=17)
            fig_ax.tick_params(labelsize=16)

    ax_shap.annotate(chosen_id,
                     xy=(-0.85, 0.5), xycoords='axes fraction',
                     rotation=90, va='center', ha='center',
                     fontsize=20, fontweight='bold', color='black',
                     clip_on=False)

    # ----------------------------------------------------------
    # KOLOM 2: ARAH KORELASI SPEARMAN
    # ----------------------------------------------------------
    spearman_rows = []
    for j, col in enumerate(X_test_clean_chosen.columns):
        feature_vals = X_test_clean_chosen.iloc[:, j]
        shap_vals = pd.Series(shap_values_plot[:, j], index=feature_vals.index)

        if feature_vals.nunique() > 1 and shap_vals.nunique() > 1:
            corr = feature_vals.corr(shap_vals, method='spearman')
            corr = 0.0 if pd.isna(corr) else corr
        else:
            corr = 0.0

        spearman_rows.append({'Feature': col, 'Spearman_Corr': corr})

    df_spearman = pd.DataFrame(spearman_rows)
    df_spearman = df_spearman.set_index('Feature').loc[top_15_features_shap].reset_index()

    colors = ['#c62828' if val > 0 else '#2e7d32' for val in df_spearman['Spearman_Corr']]

    sns.barplot(x='Spearman_Corr', y='Feature', data=df_spearman,
                hue='Feature', palette=colors, legend=False, ax=ax_spearman)

    ax_spearman.axvline(0, color='black', linewidth=1.2)
    ax_spearman.set_title("Arah Pengaruh (Spearman Correlation)", fontsize=20, fontweight='bold', pad=15)
    ax_spearman.set_ylabel('')
    ax_spearman.set_yticks([])
    ax_spearman.set_xlabel("Spearman r (Positif = Memperparah Distress)", fontsize=18)
    ax_spearman.tick_params(axis='x', labelsize=16)
    ax_spearman.locator_params(axis='x', nbins=8)

    max_abs_corr = df_spearman['Spearman_Corr'].abs().max()
    xlim_val = max(max_abs_corr * 1.15, 0.1)
    ax_spearman.set_xlim(-xlim_val, xlim_val)
    ax_spearman.margins(x=0)

    # ----------------------------------------------------------
    # KOLOM 3: FEATURE IMPORTANCE
    # ----------------------------------------------------------
    importance = chosen_model.feature_importances_
    df_imp = pd.DataFrame({'Feature': X_test_clean_chosen.columns, 'Importance': importance})
    df_imp = df_imp.sort_values(by='Importance', ascending=False).head(15)

    sns.barplot(x='Importance', y='Feature', data=df_imp, hue='Feature',
                palette='viridis', legend=False, ax=ax_imp)

    ax_imp.set_title("Top 15 Feature Importance (Model)", fontsize=20, fontweight='bold', pad=15)
    ax_imp.set_ylabel('')
    ax_imp.set_xlabel("Importance", fontsize=18)
    ax_imp.tick_params(axis='y', labelsize=18)
    ax_imp.tick_params(axis='x', labelsize=16)

# ==========================================================
# PENYESUAIAN MARGIN (kembali ke tight_layout seperti sebelumnya)
# ==========================================================
plt.tight_layout(rect=[0.08, 0, 1, 0.93])

# [FIX] Geser hanya plot kanan (Feature Importance) lebih ke kanan,
# tanpa mengubah posisi plot SHAP & Spearman
fig.canvas.draw()

SHIFT = 0.035  # seberapa jauh plot kanan digeser ke kanan (dalam koordinat figure 0-1)

for i in range(n_rows):
    pos_imp = axes[i, 2].get_position()
    new_pos_imp = [pos_imp.x0 + SHIFT, pos_imp.y0, pos_imp.width, pos_imp.height]
    axes[i, 2].set_position(new_pos_imp)

# [FIX] Gambar garis pembatas vertikal di celah antara Spearman & Feature Importance
# (posisi dihitung ulang setelah pergeseran di atas)
for i in range(n_rows):
    bbox_spearman = axes[i, 1].get_position()
    bbox_imp = axes[i, 2].get_position()

    x_sep = (bbox_spearman.x1 + bbox_imp.x0) / 2

    y0 = bbox_imp.y0 - 0.01
    y1 = bbox_imp.y1 + 0.01

    line = Line2D([x_sep, x_sep], [y0, y1], transform=fig.transFigure,
                   color='gray', linestyle='--', linewidth=1.5, alpha=0.7)
    line.set_clip_on(False)
    fig.add_artist(line)

plt.savefig("negeq_shap_summary.png", dpi=300, bbox_inches='tight')
plt.show()

for chosen_id in negeq_best_configs:

    chosen_model = dict_fitted_models[chosen_id]
    X_test_clean_chosen = dict_detailed_predictions[chosen_id]['X_test_features'].copy()
    X_test_clean_chosen = X_test_clean_chosen.fillna(np.nan).astype(float)

    explainer = shap.TreeExplainer(chosen_model)
    shap_values = explainer.shap_values(X_test_clean_chosen)

    if isinstance(shap_values, list):
        shap_values_plot = shap_values[1]
    elif len(shap_values.shape) == 3:
        shap_values_plot = shap_values[:, :, 1]
    else:
        shap_values_plot = shap_values

    # ----------------------------------------------------------
    # DATAFRAME 1: Mean |SHAP| (Top 15) + Arah Pengaruh (Spearman)
    # ----------------------------------------------------------
    mean_abs_shap = np.abs(shap_values_plot).mean(axis=0)
    df_shap_order = pd.DataFrame({
        'Feature': X_test_clean_chosen.columns,
        'Mean_Abs_SHAP': mean_abs_shap
    })

    top_15_shap = df_shap_order.sort_values(by='Mean_Abs_SHAP', ascending=False).head(15).reset_index(drop=True)

    spearman_rows = []
    for feat in top_15_shap['Feature']:
        j = list(X_test_clean_chosen.columns).index(feat)
        feature_vals = X_test_clean_chosen.iloc[:, j]
        shap_vals = pd.Series(shap_values_plot[:, j], index=feature_vals.index)

        if feature_vals.nunique() > 1 and shap_vals.nunique() > 1:
            corr = feature_vals.corr(shap_vals, method='spearman')
            corr = 0.0 if pd.isna(corr) else corr
        else:
            corr = 0.0

        spearman_rows.append(corr)

    top_15_shap['Spearman_Corr'] = spearman_rows
    top_15_shap['Arah_Pengaruh'] = top_15_shap['Spearman_Corr'].apply(
        lambda x: 'Positif (Memperparah Distress)' if x > 0
        else ('Negatif (Mengurangi Distress)' if x < 0 else 'Netral')
    )

    df1 = top_15_shap[['Feature', 'Mean_Abs_SHAP', 'Spearman_Corr', 'Arah_Pengaruh']]

    # ----------------------------------------------------------
    # DATAFRAME 2: Feature Importance Model (Top 15)
    # ----------------------------------------------------------
    importance = chosen_model.feature_importances_
    df_imp = pd.DataFrame({
        'Feature': X_test_clean_chosen.columns,
        'Importance': importance
    })

    df2 = df_imp.sort_values(by='Importance', ascending=False).head(15).reset_index(drop=True)

    # ----------------------------------------------------------
    # CETAK DALAM FORMAT CSV
    # ----------------------------------------------------------
    print(f"=== {chosen_id} - SHAP & Arah Pengaruh (Top 15) ===")
    print(df1.to_csv(index=False))

    print(f"=== {chosen_id} - Feature Importance (Top 15) ===")
    print(df2.to_csv(index=False))
=== negeq_keep_all_RandomForest - SHAP & Arah Pengaruh (Top 15) ===
Feature,Mean_Abs_SHAP,Spearman_Corr,Arah_Pengaruh
ROE,0.004625663056664717,-0.09028384156427355,Negatif (Mengurangi Distress)
Debt_Equity,0.003457320624640768,0.6055624353738713,Positif (Memperparah Distress)
EPS_proxy,0.0032708919749361246,-0.3306482822525756,Negatif (Mengurangi Distress)
NetMargin,0.003133286125230754,-0.6847803201121454,Negatif (Mengurangi Distress)
Equity_growth,0.0030112070131665183,-0.2819757070574467,Negatif (Mengurangi Distress)
ROA,0.002487984435032106,-0.5983583138075316,Negatif (Mengurangi Distress)
Equity_TA,0.0024697348061009285,-0.5822281351509317,Negatif (Mengurangi Distress)
Debt_TA,0.002347278445749217,0.740499807604793,Positif (Memperparah Distress)
CR,0.0020296119037564245,-0.48617636181032436,Negatif (Mengurangi Distress)
Altman_X3_EBIT_TA,0.0017821558608417658,0.18625940544908162,Positif (Memperparah Distress)
TL_TA,0.001777735423909399,0.6285698598388582,Positif (Memperparah Distress)
NetDebt_EBITDA,0.0016997844944600766,0.6033004773841287,Positif (Memperparah Distress)
OpMargin,0.0013898848494798453,-0.43185576323494523,Negatif (Mengurangi Distress)
Altman_X4_MVE_TL,0.0013209701057985072,-0.5787067949370781,Negatif (Mengurangi Distress)
GrossMargin,0.0010911677334193087,-0.47606274023409656,Negatif (Mengurangi Distress)

=== negeq_keep_all_RandomForest - Feature Importance (Top 15) ===
Feature,Importance
ROE,0.07100069611755966
Equity_growth,0.06043605623912042
Debt_Equity,0.05122967599484337
Equity_TA,0.050442958734987414
EPS_proxy,0.04009828086623649
TL_TA,0.033178643467025405
ROA,0.031915538384382
Debt_TA,0.03044449608401384
Altman_X3_EBIT_TA,0.02948647844209178
NetMargin,0.028301225673961216
Altman_X4_MVE_TL,0.02809476182860804
CR,0.02413421155883497
TA_growth,0.02388509213588187
Prepaid_CA,0.020550104908736416
NetDebt_EBITDA,0.0203525744947967

=== negeq_keep_corr_RandomForest - SHAP & Arah Pengaruh (Top 15) ===
Feature,Mean_Abs_SHAP,Spearman_Corr,Arah_Pengaruh
Equity_growth,0.0042040743561464625,-0.22707310613110623,Negatif (Mengurangi Distress)
NetMargin,0.00405017940099328,-0.7033373939453705,Negatif (Mengurangi Distress)
Debt_Equity,0.003993514402583375,0.5704869177255164,Positif (Memperparah Distress)
EPS_proxy,0.0034986253571296926,-0.28961897528504504,Negatif (Mengurangi Distress)
ROA,0.0034105522555389683,-0.545023371078781,Negatif (Mengurangi Distress)
Equity_TA,0.003094239129336745,-0.6155995622722693,Negatif (Mengurangi Distress)
Altman_X3_EBIT_TA,0.0025747218178525377,0.06652745322802145,Positif (Memperparah Distress)
NetDebt_EBITDA,0.002534515635427185,0.6810238979072052,Positif (Memperparah Distress)
CR,0.0022594287380385755,-0.43786851642349445,Negatif (Mengurangi Distress)
Altman_X4_MVE_TL,0.0016697428793688374,-0.5017384335362053,Negatif (Mengurangi Distress)
TA_growth,0.0016380741644945907,-0.3833115067297223,Negatif (Mengurangi Distress)
OpMargin,0.0014961122887564317,-0.28073922221711606,Negatif (Mengurangi Distress)
Age_When_IPO,0.0013613086317569828,-0.14030367907810512,Negatif (Mengurangi Distress)
CashST_CL,0.0013459002523823309,-0.7532677982576168,Negatif (Mengurangi Distress)
Parent Percent Owned (%),0.0013092594160215058,-0.13934981499767857,Negatif (Mengurangi Distress)

=== negeq_keep_corr_RandomForest - Feature Importance (Top 15) ===
Feature,Importance
Equity_growth,0.07422492609876975
Debt_Equity,0.07177956532372345
Equity_TA,0.06640534694356909
EPS_proxy,0.04123080744789114
ROA,0.037993153554151296
Altman_X3_EBIT_TA,0.0343105863536803
TA_growth,0.03296366159712036
Altman_X4_MVE_TL,0.0328021605942236
NetMargin,0.032223565549263866
CR,0.02793690781785807
NetDebt_EBITDA,0.02623796488689963
CashST_CL,0.024938653402126292
CA_TA,0.023471534710224003
PB,0.023466937967508213
Percent Owned - Insiders (%),0.02314906508943251
for chosen_id in negeq_best_configs:
    print(f"\n========== MENGANALISIS: {chosen_id} ==========")

    chosen_model = dict_fitted_models[chosen_id]
    X_test_clean_chosen = dict_detailed_predictions[chosen_id]['X_test_features'].copy()
    X_test_clean_chosen = X_test_clean_chosen.fillna(np.nan).astype(float)

    # ---------- SHAP ----------
    explainer = shap.TreeExplainer(chosen_model)
    shap_values = explainer.shap_values(X_test_clean_chosen)
    if isinstance(shap_values, list):
        shap_values_plot = shap_values[1]
    elif len(shap_values.shape) == 3:
        shap_values_plot = shap_values[:, :, 1]
    else:
        shap_values_plot = shap_values

    feature_names = list(X_test_clean_chosen.columns)
    mean_abs_shap = np.abs(shap_values_plot).mean(axis=0)
    tree_importance = chosen_model.feature_importances_

    industry_idx = [i for i, c in enumerate(feature_names) if c.startswith('industry_')]
    other_idx = [i for i, c in enumerate(feature_names) if not c.startswith('industry_')]

    rows = []
    for i in other_idx:
        feature_vals = X_test_clean_chosen.iloc[:, i]
        shap_vals = pd.Series(shap_values_plot[:, i], index=feature_vals.index)
        rows.append({
            'Feature': feature_names[i],
            'Mean_Abs_SHAP': mean_abs_shap[i],
            'Tree_Importance': tree_importance[i],
            'SHAP_Dir_Spearman': feature_vals.corr(shap_vals, method='spearman')
        })

    # ---------- Agregasi Industry Group ----------
    if industry_idx:
        industry_shap_rowsum = shap_values_plot[:, industry_idx].sum(axis=1)
        rows.append({
            'Feature': 'Industry Group (agregat)',
            'Mean_Abs_SHAP': np.abs(industry_shap_rowsum).mean(),
            'Tree_Importance': tree_importance[industry_idx].sum(),
            'SHAP_Dir_Spearman': np.nan
        })

    df_importance = pd.DataFrame(rows).sort_values(
        by='Mean_Abs_SHAP', ascending=False).reset_index(drop=True)

    print(df_importance.to_csv(index=False))

========== MENGANALISIS: negeq_keep_all_RandomForest ==========
Feature,Mean_Abs_SHAP,Tree_Importance,SHAP_Dir_Spearman
ROE,0.004625663056664717,0.07100069611755966,-0.09028384156427355
Debt_Equity,0.003457320624640768,0.05122967599484337,0.6055624353738713
EPS_proxy,0.0032708919749361246,0.04009828086623649,-0.3306482822525756
NetMargin,0.003133286125230754,0.028301225673961216,-0.6847803201121454
Equity_growth,0.0030112070131665183,0.06043605623912042,-0.2819757070574467
ROA,0.002487984435032106,0.031915538384382,-0.5983583138075316
Equity_TA,0.0024697348061009285,0.050442958734987414,-0.5822281351509317
Debt_TA,0.002347278445749217,0.03044449608401384,0.740499807604793
CR,0.0020296119037564245,0.02413421155883497,-0.48617636181032436
Altman_X3_EBIT_TA,0.0017821558608417658,0.02948647844209178,0.18625940544908162
TL_TA,0.001777735423909399,0.033178643467025405,0.6285698598388582
NetDebt_EBITDA,0.0016997844944600766,0.0203525744947967,0.6033004773841287
OpMargin,0.0013898848494798453,0.018022980143280003,-0.43185576323494523
Altman_X4_MVE_TL,0.0013209701057985072,0.02809476182860804,-0.5787067949370781
GrossMargin,0.0010911677334193087,0.016999030495405947,-0.47606274023409656
TA_growth,0.001072358251766405,0.02388509213588187,-0.3663479238497489
Age_When_IPO,0.00096299780583648,0.013741149767947007,-0.15780429416891392
EBITDA_TA,0.0009587759534408155,0.020008440365970916,-0.13557727916342216
Prepaid_CA,0.0009063775314650194,0.020550104908736416,-0.3909773867663814
QR,0.0008828069193341019,0.015716850249881367,-0.4390809198438103
Altman_X1_WC_TA,0.0008656981967813959,0.016574445059414182,-0.2530568553402044
log_Sales,0.0008512441906341653,0.01515378146191709,0.5191429964679022
Parent Percent Owned (%),0.0008387158308222245,0.011353885864986668,-0.19198877225219743
Industry Group (agregat),0.0007922169666708516,0.010635665409170075,
Percent Owned - Insiders (%),0.0007651616476443461,0.01338875579548235,-0.7864543097926102
CashST_CL,0.0007446557362443204,0.018438563801091816,-0.8052323903464262
Years_Since_IPO,0.0007338809206590943,0.014286760550338862,0.13138470720073542
WC_Sales,0.0006595009522298618,0.014620876129339022,-0.35980634358272157
Sales_per_share,0.0006517935302728322,0.014681211782639274,0.5572900332581694
Intang_TA,0.0006515533942359876,0.002613545749472631,0.16388968132915327
CL_TA,0.0006475386202304971,0.01426576083611413,0.7397415837105475
Sales_growth,0.0005532338450319876,0.015980749979910013,0.26655219065620356
NetCash_TA,0.0005526101452252547,0.010689431481777638,-0.6892431111838138
CashST_TA,0.0005467564551435648,0.01611305992924166,-0.3481127200234514
log_MktCap,0.00048370966896798997,0.017150364922742665,0.2963524478500768
log_TA,0.0004699602301179252,0.013196268832719316,0.5576627064999407
CA_TA,0.00044942084707276416,0.014318112443437609,0.20738234523191423
PPE_TA,0.0004372879093330641,0.015234898980818413,0.0418459443173421
Percent Owned - All Institutions (%),0.0004318164571666195,0.005919099931534951,-0.09577271936244595
Altman_X5_SalesTA_AssetTurnover,0.00042331276620900316,0.012643929163821274,0.6247031899465028
CFO_per_share,0.0003996605682910822,0.012927448408991299,0.5586631127038482
Inventory_CA,0.000378170053803852,0.015093618708843048,0.13746925532967466
NI_growth,0.0003549713739680101,0.013135977335408791,0.0598280159363355
CFO_growth,0.00034223895622820466,0.013583897392081181,-0.3341164846285187
CFO_Sales,0.00033636689201467485,0.01148769104846697,0.20610677646770445
CFO_TA,0.0003193117622166426,0.012801972407512985,0.27791702235373944
PB,0.0003134618951120944,0.014520599050253536,0.48899041676940236
CFO_TL,0.00031295324911258325,0.01115038158890777,0.05355277036919835


========== MENGANALISIS: negeq_keep_corr_RandomForest ==========
Feature,Mean_Abs_SHAP,Tree_Importance,SHAP_Dir_Spearman
Equity_growth,0.0042040743561464625,0.07422492609876975,-0.22707310613110623
NetMargin,0.00405017940099328,0.032223565549263866,-0.7033373939453705
Debt_Equity,0.003993514402583375,0.07177956532372345,0.5704869177255164
EPS_proxy,0.0034986253571296926,0.04123080744789114,-0.28961897528504504
ROA,0.0034105522555389683,0.037993153554151296,-0.545023371078781
Equity_TA,0.003094239129336745,0.06640534694356909,-0.6155995622722693
Altman_X3_EBIT_TA,0.0025747218178525377,0.0343105863536803,0.06652745322802145
NetDebt_EBITDA,0.002534515635427185,0.02623796488689963,0.6810238979072052
CR,0.0022594287380385755,0.02793690781785807,-0.43786851642349445
Altman_X4_MVE_TL,0.0016697428793688374,0.0328021605942236,-0.5017384335362053
TA_growth,0.0016380741644945907,0.03296366159712036,-0.3833115067297223
Industry Group (agregat),0.0015353125440069704,0.017312081068830948,
OpMargin,0.0014961122887564317,0.019172155916588784,-0.28073922221711606
Age_When_IPO,0.0013613086317569828,0.020880065720676706,-0.14030367907810512
CashST_CL,0.0013459002523823309,0.024938653402126292,-0.7532677982576168
Parent Percent Owned (%),0.0013092594160215058,0.012194492078831387,-0.13934981499767857
Percent Owned - Insiders (%),0.0012137615429222742,0.02314906508943251,-0.6252127281579721
GrossMargin,0.0011129381201764078,0.02079232612012233,-0.49690315712418703
QR,0.0011065717203374665,0.01915557528541538,-0.4232540544700148
log_Sales,0.001099384642699087,0.018949190660420938,0.6457135967734653
Prepaid_CA,0.0010069424205873655,0.021328792906561728,-0.3396681333204922
CL_TA,0.0009840885698557723,0.015120715217818265,0.7775134436472045
log_TA,0.0008925339422002186,0.01612310379661396,0.7329312291198521
Intang_TA,0.0008890484746971554,0.004986456980321322,-0.20781374781222237
Years_Since_IPO,0.0008544236436557729,0.015958806752374773,0.18986098868003295
Sales_growth,0.0007440883585414027,0.016637224589518947,0.30081991902017463
Altman_X5_SalesTA_AssetTurnover,0.0007230630170794479,0.021231124061383975,0.36377814677326153
CFO_growth,0.0007090481339836096,0.01628767835390425,-0.5360196478693756
Percent Owned - All Institutions (%),0.0007072504729634266,0.009159910768907218,0.00557173024390607
NetCash_TA,0.0006917277965492807,0.013915276682557699,-0.6136503133854758
CFO_per_share,0.0006868883456319708,0.017609153213909916,0.6733420125279201
CFO_TL,0.0006860698973694815,0.015992060397351546,-0.5424094855784702
Sales_per_share,0.0006805641658287826,0.01469472900682485,0.6288881377331633
Inventory_CA,0.0006777304286360188,0.01431133154054633,0.1923265823819316
CFO_Sales,0.0006542972042818198,0.01657146597793686,0.49564651741854193
CA_TA,0.0006508596287036028,0.023471534710224003,0.16076441498673563
CashST_TA,0.0006483634300863354,0.018717242873413857,-0.29652366496167026
log_MktCap,0.0006368742568308768,0.016435305385496545,0.07118306941874974
PB,0.0006194434309161633,0.023466937967508213,0.3605438703534683
PPE_TA,0.0005949782009590698,0.020042985801808064,0.07306067007117537
NI_growth,0.0005667111544413911,0.013285911505421993,0.08512811125881825

FI Target Distress Rugi Berturut-turut

conloss_best_configs = ['conloss_keep_corr_CatBoost','conloss_keep_all_XGBoost']
# [FIX] Sembunyikan warning tight_layout (tidak berbahaya, hanya informasi)
warnings.filterwarnings("ignore", message="This figure includes Axes that are not compatible with tight_layout")

# [FIX] Set DPI tinggi & font dasar lebih besar
plt.rcParams['figure.dpi'] = 150
plt.rcParams['savefig.dpi'] = 300
plt.rcParams['font.size'] = 16

n_rows = len(conloss_best_configs)
n_cols = 3

# [FIX] Kanvas diperlebar agar plot kanan punya ruang untuk digeser ke kanan
fig, axes = plt.subplots(nrows=n_rows, ncols=n_cols, figsize=(33, 8 * n_rows),
                         gridspec_kw={'width_ratios': [1.3, 1.55, 1.05], 'wspace': 0.18})

fig.suptitle("Pengaruh dan Kontribusi Fitur\nIndikator Rugi Berturut-turut",
              fontsize=26, fontweight='bold', y=0.97)

axes = np.atleast_2d(axes)

for i, chosen_id in enumerate(conloss_best_configs):

    ax_shap = axes[i, 0]
    ax_spearman = axes[i, 1]
    ax_imp = axes[i, 2]

    chosen_model = dict_fitted_models[chosen_id]
    X_test_clean_chosen = dict_detailed_predictions[chosen_id]['X_test_features'].copy()
    X_test_clean_chosen = X_test_clean_chosen.fillna(np.nan).astype(float)

    explainer = shap.TreeExplainer(chosen_model)
    shap_values = explainer.shap_values(X_test_clean_chosen)

    if isinstance(shap_values, list):
        shap_values_plot = shap_values[1]
    elif len(shap_values.shape) == 3:
        shap_values_plot = shap_values[:, :, 1]
    else:
        shap_values_plot = shap_values

    mean_abs_shap = np.abs(shap_values_plot).mean(axis=0)
    df_shap_order = pd.DataFrame({'Feature': X_test_clean_chosen.columns, 'Mean_Abs_SHAP': mean_abs_shap})
    top_15_features_shap = df_shap_order.sort_values(by='Mean_Abs_SHAP', ascending=False)['Feature'].head(15).tolist()

    # ----------------------------------------------------------
    # KOLOM 1: SHAP PLOT
    # ----------------------------------------------------------
    plt.sca(ax_shap)

    shap.summary_plot(shap_values_plot, X_test_clean_chosen, max_display=15,
                      show=False, plot_size=None)

    ax_shap.set_title("SHAP Values Summary", fontsize=20, fontweight='bold', pad=15)

    if "RandomForest" in chosen_model.__class__.__name__:
        ax_shap.set_xlabel("SHAP value (Impact on Probability)", fontsize=18)
    else:
        ax_shap.set_xlabel("SHAP value (Impact on Log-Odds)", fontsize=18)

    ax_shap.tick_params(axis='y', labelsize=18)
    ax_shap.tick_params(axis='x', labelsize=16)

    for fig_ax in fig.axes:
        if fig_ax.get_ylabel() == "Feature value":
            fig_ax.set_ylabel("Feature value", fontsize=17)
            fig_ax.tick_params(labelsize=16)

    ax_shap.annotate(chosen_id,
                     xy=(-0.85, 0.5), xycoords='axes fraction',
                     rotation=90, va='center', ha='center',
                     fontsize=20, fontweight='bold', color='black',
                     clip_on=False)

    # ----------------------------------------------------------
    # KOLOM 2: ARAH KORELASI SPEARMAN
    # ----------------------------------------------------------
    spearman_rows = []
    for j, col in enumerate(X_test_clean_chosen.columns):
        feature_vals = X_test_clean_chosen.iloc[:, j]
        shap_vals = pd.Series(shap_values_plot[:, j], index=feature_vals.index)

        if feature_vals.nunique() > 1 and shap_vals.nunique() > 1:
            corr = feature_vals.corr(shap_vals, method='spearman')
            corr = 0.0 if pd.isna(corr) else corr
        else:
            corr = 0.0

        spearman_rows.append({'Feature': col, 'Spearman_Corr': corr})

    df_spearman = pd.DataFrame(spearman_rows)
    df_spearman = df_spearman.set_index('Feature').loc[top_15_features_shap].reset_index()

    colors = ['#c62828' if val > 0 else '#2e7d32' for val in df_spearman['Spearman_Corr']]

    sns.barplot(x='Spearman_Corr', y='Feature', data=df_spearman,
                hue='Feature', palette=colors, legend=False, ax=ax_spearman)

    ax_spearman.axvline(0, color='black', linewidth=1.2)
    ax_spearman.set_title("Arah Pengaruh (Spearman Correlation)", fontsize=20, fontweight='bold', pad=15)
    ax_spearman.set_ylabel('')
    ax_spearman.set_yticks([])
    ax_spearman.set_xlabel("Spearman r (Positif = Memperparah Distress)", fontsize=18)
    ax_spearman.tick_params(axis='x', labelsize=16)
    ax_spearman.locator_params(axis='x', nbins=8)

    max_abs_corr = df_spearman['Spearman_Corr'].abs().max()
    xlim_val = max(max_abs_corr * 1.15, 0.1)
    ax_spearman.set_xlim(-xlim_val, xlim_val)
    ax_spearman.margins(x=0)

    # ----------------------------------------------------------
    # KOLOM 3: FEATURE IMPORTANCE
    # ----------------------------------------------------------
    importance = chosen_model.feature_importances_
    df_imp = pd.DataFrame({'Feature': X_test_clean_chosen.columns, 'Importance': importance})
    df_imp = df_imp.sort_values(by='Importance', ascending=False).head(15)

    sns.barplot(x='Importance', y='Feature', data=df_imp, hue='Feature',
                palette='viridis', legend=False, ax=ax_imp)

    ax_imp.set_title("Top 15 Feature Importance (Model)", fontsize=20, fontweight='bold', pad=15)
    ax_imp.set_ylabel('')
    ax_imp.set_xlabel("Importance", fontsize=18)
    ax_imp.tick_params(axis='y', labelsize=18)
    ax_imp.tick_params(axis='x', labelsize=16)

# ==========================================================
# PENYESUAIAN MARGIN (kembali ke tight_layout seperti sebelumnya)
# ==========================================================
plt.tight_layout(rect=[0.08, 0, 1, 0.93])

# [FIX] Geser hanya plot kanan (Feature Importance) lebih ke kanan,
# tanpa mengubah posisi plot SHAP & Spearman
fig.canvas.draw()

SHIFT = 0.035  # seberapa jauh plot kanan digeser ke kanan (dalam koordinat figure 0-1)

for i in range(n_rows):
    pos_imp = axes[i, 2].get_position()
    new_pos_imp = [pos_imp.x0 + SHIFT, pos_imp.y0, pos_imp.width, pos_imp.height]
    axes[i, 2].set_position(new_pos_imp)

# [FIX] Gambar garis pembatas vertikal di celah antara Spearman & Feature Importance
# (posisi dihitung ulang setelah pergeseran di atas)
for i in range(n_rows):
    bbox_spearman = axes[i, 1].get_position()
    bbox_imp = axes[i, 2].get_position()

    x_sep = (bbox_spearman.x1 + bbox_imp.x0) / 2

    y0 = bbox_imp.y0 - 0.01
    y1 = bbox_imp.y1 + 0.01

    line = Line2D([x_sep, x_sep], [y0, y1], transform=fig.transFigure,
                   color='gray', linestyle='--', linewidth=1.5, alpha=0.7)
    line.set_clip_on(False)
    fig.add_artist(line)

plt.savefig("conloss_shap_summary.png", dpi=300, bbox_inches='tight')
plt.show()

for chosen_id in conloss_best_configs:

    chosen_model = dict_fitted_models[chosen_id]
    X_test_clean_chosen = dict_detailed_predictions[chosen_id]['X_test_features'].copy()
    X_test_clean_chosen = X_test_clean_chosen.fillna(np.nan).astype(float)

    explainer = shap.TreeExplainer(chosen_model)
    shap_values = explainer.shap_values(X_test_clean_chosen)

    if isinstance(shap_values, list):
        shap_values_plot = shap_values[1]
    elif len(shap_values.shape) == 3:
        shap_values_plot = shap_values[:, :, 1]
    else:
        shap_values_plot = shap_values

    # ----------------------------------------------------------
    # DATAFRAME 1: Mean |SHAP| (Top 15) + Arah Pengaruh (Spearman)
    # ----------------------------------------------------------
    mean_abs_shap = np.abs(shap_values_plot).mean(axis=0)
    df_shap_order = pd.DataFrame({
        'Feature': X_test_clean_chosen.columns,
        'Mean_Abs_SHAP': mean_abs_shap
    })

    top_15_shap = df_shap_order.sort_values(by='Mean_Abs_SHAP', ascending=False).head(15).reset_index(drop=True)

    spearman_rows = []
    for feat in top_15_shap['Feature']:
        j = list(X_test_clean_chosen.columns).index(feat)
        feature_vals = X_test_clean_chosen.iloc[:, j]
        shap_vals = pd.Series(shap_values_plot[:, j], index=feature_vals.index)

        if feature_vals.nunique() > 1 and shap_vals.nunique() > 1:
            corr = feature_vals.corr(shap_vals, method='spearman')
            corr = 0.0 if pd.isna(corr) else corr
        else:
            corr = 0.0

        spearman_rows.append(corr)

    top_15_shap['Spearman_Corr'] = spearman_rows
    top_15_shap['Arah_Pengaruh'] = top_15_shap['Spearman_Corr'].apply(
        lambda x: 'Positif (Memperparah Distress)' if x > 0
        else ('Negatif (Mengurangi Distress)' if x < 0 else 'Netral')
    )

    df1 = top_15_shap[['Feature', 'Mean_Abs_SHAP', 'Spearman_Corr', 'Arah_Pengaruh']]

    # ----------------------------------------------------------
    # DATAFRAME 2: Feature Importance Model (Top 15)
    # ----------------------------------------------------------
    importance = chosen_model.feature_importances_
    df_imp = pd.DataFrame({
        'Feature': X_test_clean_chosen.columns,
        'Importance': importance
    })

    df2 = df_imp.sort_values(by='Importance', ascending=False).head(15).reset_index(drop=True)

    # ----------------------------------------------------------
    # CETAK DALAM FORMAT CSV
    # ----------------------------------------------------------
    print(f"=== {chosen_id} - SHAP & Arah Pengaruh (Top 15) ===")
    print(df1.to_csv(index=False))

    print(f"=== {chosen_id} - Feature Importance (Top 15) ===")
    print(df2.to_csv(index=False))
=== conloss_keep_corr_CatBoost - SHAP & Arah Pengaruh (Top 15) ===
Feature,Mean_Abs_SHAP,Spearman_Corr,Arah_Pengaruh
ROE,0.3192689554432316,-0.8952229046183492,Negatif (Mengurangi Distress)
NetMargin,0.2373379568824317,-0.9435595324985789,Negatif (Mengurangi Distress)
Altman_X3_EBIT_TA,0.22213192867238066,-0.9627948631943598,Negatif (Mengurangi Distress)
Percent Owned - All Institutions (%),0.16776538964882504,-0.5198438911560657,Negatif (Mengurangi Distress)
NetDebt_EBITDA,0.15207264527761338,0.8415742661145861,Positif (Memperparah Distress)
EPS_proxy,0.1479449232133265,-0.8174231548219381,Negatif (Mengurangi Distress)
ROA,0.14079531854817653,-0.9512575181143631,Negatif (Mengurangi Distress)
NI_growth,0.1290547993123681,-0.802806912946197,Negatif (Mengurangi Distress)
Years_Since_IPO,0.1213380976819955,-0.816997763187094,Negatif (Mengurangi Distress)
Altman_X5_SalesTA_AssetTurnover,0.10294381176679801,-0.7030643800875506,Negatif (Mengurangi Distress)
log_MktCap,0.09891725291081024,0.1908580690421838,Positif (Memperparah Distress)
Debt_TA,0.09099207539146018,0.8958160029127759,Positif (Memperparah Distress)
CFO_growth,0.08901102062332121,-0.23898014755267694,Negatif (Mengurangi Distress)
PPE_TA,0.08485142283635062,0.9009417732751318,Positif (Memperparah Distress)
CFO_TL,0.08244002475332127,-0.9224849321072246,Negatif (Mengurangi Distress)

=== conloss_keep_corr_CatBoost - Feature Importance (Top 15) ===
Feature,Importance
NetMargin,6.037625911004771
ROE,5.763679919701531
EPS_proxy,3.8768890456041323
Years_Since_IPO,3.827685140479139
Altman_X3_EBIT_TA,3.731992848718549
ROA,3.7155374071722242
Age_When_IPO,3.5891166830543133
NetDebt_EBITDA,3.403282666506999
Debt_TA,3.3585462442856215
log_MktCap,3.2865612655214806
Percent Owned - All Institutions (%),3.259183293903597
Altman_X5_SalesTA_AssetTurnover,3.0476374269120967
NI_growth,2.885804311365791
OpMargin,2.5292780190769526
Inventory_CA,2.3960990758577188

=== conloss_keep_all_XGBoost - SHAP & Arah Pengaruh (Top 15) ===
Feature,Mean_Abs_SHAP,Spearman_Corr,Arah_Pengaruh
ROE,0.4316161,-0.8800784886405073,Negatif (Mengurangi Distress)
Altman_X3_EBIT_TA,0.27861458,-0.9386388248497058,Negatif (Mengurangi Distress)
ROA,0.26279235,-0.8486209109277847,Negatif (Mengurangi Distress)
NetMargin,0.24395414,-0.89332692210077,Negatif (Mengurangi Distress)
EPS_proxy,0.21610238,-0.8602264211128476,Negatif (Mengurangi Distress)
NI_growth,0.20682013,-0.7581126741019758,Negatif (Mengurangi Distress)
NetDebt_EBITDA,0.14913501,0.8130785804095254,Positif (Memperparah Distress)
Percent Owned - All Institutions (%),0.13340995,0.20476863161164088,Positif (Memperparah Distress)
Debt_TA,0.12957084,0.8029082235944924,Positif (Memperparah Distress)
Parent Percent Owned (%),0.1223252,0.5858002055997794,Positif (Memperparah Distress)
Years_Since_IPO,0.12201359,-0.6081864318512272,Negatif (Mengurangi Distress)
Inventory_CA,0.10511425,-0.8890527287382054,Negatif (Mengurangi Distress)
TA_growth,0.09546359,-0.6552825594653804,Negatif (Mengurangi Distress)
log_MktCap,0.0877707,-0.11755728448771045,Negatif (Mengurangi Distress)
CFO_TA,0.07471729,-0.3362505130075218,Negatif (Mengurangi Distress)

=== conloss_keep_all_XGBoost - Feature Importance (Top 15) ===
Feature,Importance
ROA,0.10206569
ROE,0.050763775
NetMargin,0.04683309
EPS_proxy,0.046610106
industry_Capital Goods,0.042293403
Altman_X3_EBIT_TA,0.022063222
EBITDA_TA,0.017023806
Debt_TA,0.016601881
Debt_Equity,0.016467342
Equity_growth,0.016464878
CashST_CL,0.016215578
NI_growth,0.016118083
GrossMargin,0.0154362405
NetDebt_EBITDA,0.015100167
CL_TA,0.015058582
for chosen_id in conloss_best_configs:
    print(f"\n========== MENGANALISIS: {chosen_id} ==========")

    chosen_model = dict_fitted_models[chosen_id]
    X_test_clean_chosen = dict_detailed_predictions[chosen_id]['X_test_features'].copy()
    X_test_clean_chosen = X_test_clean_chosen.fillna(np.nan).astype(float)

    # ---------- SHAP ----------
    explainer = shap.TreeExplainer(chosen_model)
    shap_values = explainer.shap_values(X_test_clean_chosen)
    if isinstance(shap_values, list):
        shap_values_plot = shap_values[1]
    elif len(shap_values.shape) == 3:
        shap_values_plot = shap_values[:, :, 1]
    else:
        shap_values_plot = shap_values

    feature_names = list(X_test_clean_chosen.columns)
    mean_abs_shap = np.abs(shap_values_plot).mean(axis=0)
    tree_importance = chosen_model.feature_importances_

    industry_idx = [i for i, c in enumerate(feature_names) if c.startswith('industry_')]
    other_idx = [i for i, c in enumerate(feature_names) if not c.startswith('industry_')]

    rows = []
    for i in other_idx:
        feature_vals = X_test_clean_chosen.iloc[:, i]
        shap_vals = pd.Series(shap_values_plot[:, i], index=feature_vals.index)
        rows.append({
            'Feature': feature_names[i],
            'Mean_Abs_SHAP': mean_abs_shap[i],
            'Tree_Importance': tree_importance[i],
            'SHAP_Dir_Spearman': feature_vals.corr(shap_vals, method='spearman')
        })

    # ---------- Agregasi Industry Group ----------
    if industry_idx:
        industry_shap_rowsum = shap_values_plot[:, industry_idx].sum(axis=1)
        rows.append({
            'Feature': 'Industry Group (agregat)',
            'Mean_Abs_SHAP': np.abs(industry_shap_rowsum).mean(),
            'Tree_Importance': tree_importance[industry_idx].sum(),
            'SHAP_Dir_Spearman': np.nan
        })

    df_importance = pd.DataFrame(rows).sort_values(
        by='Mean_Abs_SHAP', ascending=False).reset_index(drop=True)

    print(df_importance.to_csv(index=False))

========== MENGANALISIS: conloss_keep_corr_CatBoost ==========
Feature,Mean_Abs_SHAP,Tree_Importance,SHAP_Dir_Spearman
ROE,0.3192689554432316,5.763679919701531,-0.8952229046183492
NetMargin,0.2373379568824317,6.037625911004771,-0.9435595324985789
Altman_X3_EBIT_TA,0.22213192867238066,3.731992848718549,-0.9627948631943598
Percent Owned - All Institutions (%),0.16776538964882504,3.259183293903597,-0.5198438911560657
NetDebt_EBITDA,0.15207264527761338,3.403282666506999,0.8415742661145861
EPS_proxy,0.1479449232133265,3.8768890456041323,-0.8174231548219381
ROA,0.14079531854817653,3.7155374071722242,-0.9512575181143631
NI_growth,0.1290547993123681,2.885804311365791,-0.802806912946197
Years_Since_IPO,0.1213380976819955,3.827685140479139,-0.816997763187094
Altman_X5_SalesTA_AssetTurnover,0.10294381176679801,3.0476374269120967,-0.7030643800875506
log_MktCap,0.09891725291081024,3.2865612655214806,0.1908580690421838
Debt_TA,0.09099207539146018,3.3585462442856215,0.8958160029127759
CFO_growth,0.08901102062332121,2.3409274711567254,-0.23898014755267694
PPE_TA,0.08485142283635062,2.180472887780791,0.9009417732751318
CFO_TL,0.08244002475332127,2.1790389695776953,-0.9224849321072246
Parent Percent Owned (%),0.08072765208012775,2.1722411277249116,-0.772896316086723
OpMargin,0.0764415239155485,2.5292780190769526,-0.8860624666567034
Equity_growth,0.07390574119168808,1.4694937235361858,-0.908026329474901
Inventory_CA,0.07141082093973754,2.3960990758577188,-0.5336843138463242
Age_When_IPO,0.06222695774740363,3.5891166830543133,-0.39621451882091596
TA_growth,0.06200531341097385,2.3603542566171285,-0.6713826001367498
CashST_CL,0.05967316622299798,1.5657743322192281,-0.6433215400283528
CashST_TA,0.057680932965038526,1.2566718809770105,-0.8774946352882733
CL_TA,0.05733519148380391,1.7617903188767938,-0.8522335869914557
Industry Group (agregat),0.05108101273762465,2.399768603489172,
Sales_per_share,0.04890856935668006,2.106615419133713,0.7575253911196369
CFO_per_share,0.043965296534763464,1.4783948300223175,-0.46090024233949795
log_TA,0.043565420091639315,1.6291003514201758,-0.7575893266943587
CFO_Sales,0.041514208680477366,1.4059646598096232,-0.8134293127440791
log_Sales,0.03722292302281421,1.2159258247822857,-0.8136583834787535
Intang_TA,0.03627352522085023,0.46218669436723164,0.1575413253279351
Percent Owned - Insiders (%),0.035134232141018656,1.6762910694682036,0.1358865277773194
GrossMargin,0.03420233937295071,1.8209861259140958,-0.31349617272009356
NetCash_TA,0.03269045663763407,1.5368259812917853,-0.4447097880359094
Sales_growth,0.03182455507260002,1.1129212648422864,-0.7393389055975017
PB,0.028345282650559325,1.7963560029930044,0.6123520609341918
QR,0.02820503551036768,1.6304430389360658,-0.2163807976769637
Altman_X4_MVE_TL,0.0267483705970156,1.40087678132163,0.3763154788439101
CR,0.0263171112896359,1.4005265172557855,-0.36445856018002987
WC_Sales,0.025180403835318037,1.231547006368211,0.11841034051149121
Prepaid_CA,0.020882543582918658,1.448516451712597,-0.13096032900595733
CA_TA,0.020047968145997516,1.466246217921986,-0.014229593434823712
Equity_TA,0.01390946369029179,0.7848229313184015,-0.2536934321039363


========== MENGANALISIS: conloss_keep_all_XGBoost ==========
Feature,Mean_Abs_SHAP,Tree_Importance,SHAP_Dir_Spearman
ROE,0.4316161,0.050763775,-0.8800784886405073
Altman_X3_EBIT_TA,0.27861458,0.022063222,-0.9386388248497058
ROA,0.26279235,0.10206569,-0.8486209109277847
NetMargin,0.24395414,0.04683309,-0.89332692210077
EPS_proxy,0.21610238,0.046610106,-0.8602264211128476
NI_growth,0.20682013,0.016118083,-0.7581126741019758
NetDebt_EBITDA,0.14913501,0.015100167,0.8130785804095254
Percent Owned - All Institutions (%),0.13340995,0.0123126805,0.20476863161164088
Debt_TA,0.12957084,0.016601881,0.8029082235944924
Parent Percent Owned (%),0.1223252,0.0124470955,0.5858002055997794
Years_Since_IPO,0.12201359,0.014892352,-0.6081864318512272
Inventory_CA,0.10511425,0.014580792,-0.8890527287382054
TA_growth,0.09546359,0.013834398,-0.6552825594653804
log_MktCap,0.0877707,0.012732381,-0.11755728448771045
CFO_TA,0.07471729,0.012783064,-0.3362505130075218
EBITDA_TA,0.07147601,0.017023806,-0.3355893286689183
Equity_growth,0.070282534,0.016464878,-0.7616637508156372
OpMargin,0.06992085,0.01478731,-0.9102186849938481
CFO_growth,0.068744965,0.013122952,-0.3334368566395375
Altman_X5_SalesTA_AssetTurnover,0.06794302,0.014147317,-0.6436615292227884
CashST_CL,0.05771726,0.016215578,-0.853047135083681
Percent Owned - Insiders (%),0.056751896,0.01242601,0.6513684278141695
Intang_TA,0.055529505,0.012313991,-0.2621616760442048
PPE_TA,0.054736733,0.012742097,0.7375466098131811
PB,0.05009698,0.014093289,0.8478996700005101
Debt_Equity,0.049957793,0.016467342,0.7761299520554691
WC_Sales,0.049925398,0.013046193,0.807391339748351
Industry Group (agregat),0.04668503,0.15102921,
CashST_TA,0.04609975,0.011933475,-0.7600641165697042
TL_TA,0.042747002,0.014053993,-0.8764548551626897
Prepaid_CA,0.0402919,0.013951697,-0.22968063564815547
NetCash_TA,0.037167843,0.013336075,-0.5737072525508242
Altman_X1_WC_TA,0.037121613,0.014401976,0.49531502608174477
Age_When_IPO,0.036031056,0.013671853,0.5992003227947154
Sales_growth,0.0357716,0.012587108,-0.7729751233853595
Sales_per_share,0.034883704,0.012403338,0.6532586886087574
GrossMargin,0.03321539,0.0154362405,0.010650029818728913
CFO_TL,0.032796174,0.013836142,-0.2660556104895719
CR,0.03105291,0.01333415,0.11841758147087618
CA_TA,0.02863456,0.0121469535,-0.0176961854796148
CFO_Sales,0.027253969,0.012090952,-0.3836491794952272
CFO_per_share,0.02665546,0.012733933,-0.09109293438925303
log_TA,0.025196731,0.011968922,-0.17522892227826747
Altman_X4_MVE_TL,0.021575313,0.013938266,0.825447794877386
QR,0.021166923,0.014942494,-0.27330286552938704
Equity_TA,0.01922914,0.012268942,0.33077153994456526
log_Sales,0.018563671,0.012286092,0.2791326759530095
CL_TA,0.0167992,0.015058582,-0.3807544782410951

Confusion Matrix Visualisasi

chosen_id_list = [
    # --- BARIS 1 (Diisi berurutan dari kiri ke kanan) ---
    'ppk_keep_all_RandomForest',    # Masuk ke Kotak 1 (Kiri Atas)
    'negeq_keep_all_RandomForest',  # Masuk ke Kotak 2 (Tengah Atas)
    'conloss_keep_all_XGBoost',     # Masuk ke Kotak 3 (Kanan Atas)
    
    # --- BARIS 2 (Turun ke bawah, diisi dari kiri ke kanan) ---
    'ppk_keep_corr_CatBoost',       # Masuk ke Kotak 4 (Kiri Bawah)
    'negeq_keep_corr_RandomForest', # Masuk ke Kotak 5 (Tengah Bawah)
    'conloss_keep_corr_CatBoost'    # Masuk ke Kotak 6 (Kanan Bawah)
]
# Gunakan constrained_layout=True agar susunan otomatis rapi
fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(16, 9), constrained_layout=True)

fig.suptitle('Evaluasi Confusion Matrix Model Final per Indikator Financial Distress', 
             fontsize=18, fontweight='bold')

axes = axes.flatten()

# Penamaan judul kolom secara manual
kolom_titles = [
    "Papan Pemantauan Khusus", 
    "Ekuitas Negatif", 
    "Rugi Berturut-turut"
]

for idx, chosen_id in enumerate(chosen_id_list):
    # Tarik data prediksi
    y_true = dict_detailed_predictions[chosen_id]['y_true']
    y_pred = dict_detailed_predictions[chosen_id]['y_pred']

    # Hitung Confusion Matrix
    cm = confusion_matrix(y_true, y_pred)

    # Plot Heatmap
    sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', 
                xticklabels=['Sehat (0)', 'Distress (1)'],
                yticklabels=['Sehat (0)', 'Distress (1)'],
                annot_kws={"size": 14}, 
                ax=axes[idx])
    
    # Penggabungan Judul dengan Modifikasi Jarak
    if idx < 3:
        target_name = kolom_titles[idx]
        axes[idx].set_title(f"\nIndikator : {target_name}\n\n{chosen_id}", fontsize=11, fontweight='bold')
    else:
        # Untuk baris kedua, tidak perlu diberi \n di awal
        axes[idx].set_title(chosen_id, fontsize=11, fontweight='bold')
    
    axes[idx].set_ylabel('Aktual')
    axes[idx].set_xlabel('Prediksi')

plt.show()

Analisis Sebaran Prediksi Distress Papan Pemantauan Khusus

ppk_keep_all_RandomForest

chosen_id = 'ppk_keep_all_RandomForest'
dict_detailed_predictions[chosen_id].keys()
dict_keys(['metadata', 'y_true', 'y_pred', 'y_prob', 'X_test_features'])
data_pred = dict_detailed_predictions[chosen_id]

# gabung jadi 1 df
df_analyze = data_pred['metadata'].copy()
df_analyze['True_Label'] = data_pred['y_true']
df_analyze['Predicted_Label'] = data_pred['y_pred']
df_analyze['Prediction_Prob'] = data_pred['y_prob']
df_analyze = pd.concat([df_analyze, data_pred['X_test_features']], axis=1)

# FN
df_fn = df_analyze[(df_analyze['True_Label'] == 1) & (df_analyze['Predicted_Label'] == 0)]
df_fn = df_fn.sort_values(by='Prediction_Prob', ascending=False)

print(f"Total False Negative ({chosen_id}): {len(df_fn)} emiten")
display(df_fn[['ticker', 'Year', 'True_Label', 'Predicted_Label', 'Prediction_Prob']].head(10))

# FP
df_fp = df_analyze[(df_analyze['True_Label'] == 0) & (df_analyze['Predicted_Label'] == 1)]
df_fp = df_fp.sort_values(by='Prediction_Prob', ascending=False)

print(f"\nTotal False Positive ({chosen_id}): {len(df_fp)} emiten")
display(df_fp[['ticker', 'Year', 'True_Label', 'Predicted_Label', 'Prediction_Prob']].head(10))
Total False Negative (ppk_keep_all_RandomForest): 28 emiten
ticker Year True_Label Predicted_Label Prediction_Prob
545 SKLT 2023 1 0 0.102213
636 WIFI 2023 1 0 0.101544
399 MGLV 2023 1 0 0.096362
503 PYFA 2023 1 0 0.092915
563 SOSS 2023 1 0 0.092372
551 SMDM 2023 1 0 0.092151
130 BTON 2023 1 0 0.090695
189 DMND 2023 1 0 0.090122
2 ABDA 2023 1 0 0.090054
481 POLU 2023 1 0 0.089773

Total False Positive (ppk_keep_all_RandomForest): 175 emiten
ticker Year True_Label Predicted_Label Prediction_Prob
501 PURI 2023 0 1 0.351597
436 NATO 2023 0 1 0.345191
491 PSDN 2023 0 1 0.326002
123 BRMS 2023 0 1 0.324656
455 PAMG 2023 0 1 0.322508
489 PRIM 2023 0 1 0.317136
185 DIVA 2023 0 1 0.315250
606 TRGU 2023 0 1 0.313993
374 LUCY 2023 0 1 0.308776
69 BAPA 2023 0 1 0.298206
detail = dict_detailed_predictions[chosen_id]

X_test = detail['X_test_features'].copy().fillna(np.nan).astype(float)
y_true = pd.Series(np.asarray(detail['y_true']), index=X_test.index)   # sesuaikan nama key label asli
y_pred = pd.Series(np.asarray(detail['y_pred']), index=X_test.index)   # sesuaikan nama key kelas akhir setelah threshold

# Kelompokkan menjadi TN / FP / FN / TP
group = np.where((y_true==0) & (y_pred==0), 'TN',
        np.where((y_true==0) & (y_pred==1), 'FP',
        np.where((y_true==1) & (y_pred==0), 'FN', 'TP')))
group = pd.Series(group, index=X_test.index, name='group')

# Bandingkan median rasio kunci antar kelompok (sesuaikan dengan fitur dominan tiap indikator)
key_ratios = ['ROE','NetMargin','ROA','Altman_X3_EBIT_TA','NetDebt_EBITDA',
              'Debt_TA','CR','log_MktCap','Sales_growth']
key_ratios = [c for c in key_ratios if c in X_test.columns]

summary = X_test[key_ratios].groupby(group).median().T
print(f"{chosen_id} :")
print(summary.to_csv())
print(group.value_counts().to_csv())   # jumlah tiap kelompok
ppk_keep_all_RandomForest :
,FN,FP,TN,TP
ROE,0.06729980021381832,0.012779070173507958,0.09899950850764239,0.012311284126411488
NetMargin,0.08545301006744334,0.015416713079188772,0.11281632748782558,0.006069147527492284
ROA,0.0373286219082203,0.007687880597593954,0.0545393249490186,0.002300934205812785
Altman_X3_EBIT_TA,0.06799366819128755,0.020966144615435098,0.08732108511965313,0.011865895257493377
NetDebt_EBITDA,0.3697371800428746,2.1265031297108754,0.28439978762948187,0.6903017626390058
Debt_TA,0.13421265127934168,0.1863485439635218,0.14066510661028264,0.22171117497587162
CR,2.107,1.6165,1.8330000000000002,1.3435000000000001
log_MktCap,14.705208754988682,13.231576462379438,15.242326335419186,12.952733626363845
Sales_growth,0.010634273318872012,0.08185789455747872,0.019928757002864894,-0.025013078631246688

group,count
TN,270
TP,178
FP,175
FN,28
GROUP_ORDER = ['TN', 'FP', 'FN', 'TP']

# Dictionary konfigurasi warna & pola strip
GROUP_CONFIG = {
    'TN': {'facecolor': '#2e7d32', 'edgecolor': '#2e7d32', 'hatch': ''},     # Hijau Polos
    'TP': {'facecolor': '#c62828', 'edgecolor': '#c62828', 'hatch': ''},     # Merah Polos
    
    # FP: Dasar Hijau, Strip Merah
    'FP': {'facecolor': '#2e7d32', 'edgecolor': '#c62828', 'hatch': r'\\'},  
    
    # FN: Dasar Merah, Strip Hijau
    'FN': {'facecolor': '#c62828', 'edgecolor': '#2e7d32', 'hatch': r'//'}   
}

# [PERBAIKAN] Nilai n_cols diubah default-nya menjadi 5
def plot_group_profiles(summary, title, n_cols=5):
    # summary: DataFrame dengan index = rasio dan kolom memuat TN/TP/FP/FN
    cols = [g for g in GROUP_ORDER if g in summary.columns]
    ratios = list(summary.index)
    n = len(ratios)
    
    # n_rows akan otomatis menyesuaikan (misal: 15 rasio / 5 kolom = 3 baris)
    n_rows = int(np.ceil(n / n_cols))
    
    # Ukuran figsize otomatis melebar ke samping (5 * 3.3 = 16.5 inci)
    fig, axes = plt.subplots(n_rows, n_cols, figsize=(n_cols * 3.3, n_rows * 2.7))
    axes = np.array(axes).reshape(-1)
    
    for i, ratio in enumerate(ratios):
        ax = axes[i]
        vals = [summary.loc[ratio, g] for g in cols]
        
        # Plot bar standar
        bars = ax.bar(cols, vals)
        
        # Kustomisasi warna dasar dan pola strip per boks grafik
        for b, g in zip(bars, cols):
            b.set_facecolor(GROUP_CONFIG[g]['facecolor'])
            b.set_edgecolor(GROUP_CONFIG[g]['edgecolor'])
            b.set_hatch(GROUP_CONFIG[g]['hatch'])
            b.set_linewidth(1.2)
            
        ax.set_title(ratio, fontsize=9)
        ax.axhline(0, color='gray', linewidth=0.6)
        ax.tick_params(axis='both', labelsize=8)
        
        for b, v in zip(bars, vals):
            ax.annotate(f"{v:.3f}", (b.get_x() + b.get_width() / 2, v),
                        ha='center', va='bottom' if v >= 0 else 'top', fontsize=10)
                        
    # Menghapus sumbu koordinat untuk kotak grid yang kosong di akhir
    for j in range(n, len(axes)):
        axes[j].axis('off')
        
    # Kustomisasi Kotak Legend
    handles = [
        plt.Rectangle((0, 0), 1, 1, 
                      facecolor=GROUP_CONFIG[g]['facecolor'], 
                      edgecolor=GROUP_CONFIG[g]['edgecolor'], 
                      hatch=GROUP_CONFIG[g]['hatch']) 
        for g in cols
    ]
    
    fig.legend(handles, cols, loc='lower center', ncol=len(cols), fontsize=10)
    fig.suptitle(title, fontsize=14)
    fig.tight_layout(rect=[0, 0.05, 1, 0.97])
    plt.show()
    return fig

group_summaries = {}            # buat sebelum loop

# di dalam loop, setelah baris summary = X_test[key_ratios].groupby(group).median().T
group_summaries[chosen_id] = summary

# setelah loop selesai
for cid, s in group_summaries.items():
    plot_group_profiles(s, f"Perbandingan Rerata Fitur Tiap Kuadran Confusion Matrix\n{cid}")

ppk_keep_corr_CatBoost

chosen_id = 'ppk_keep_corr_CatBoost'
data_pred = dict_detailed_predictions[chosen_id]

# gabung jadi 1 df
df_analyze = data_pred['metadata'].copy()
df_analyze['True_Label'] = data_pred['y_true']
df_analyze['Predicted_Label'] = data_pred['y_pred']
df_analyze['Prediction_Prob'] = data_pred['y_prob']
df_analyze = pd.concat([df_analyze, data_pred['X_test_features']], axis=1)

# FN
df_fn = df_analyze[(df_analyze['True_Label'] == 1) & (df_analyze['Predicted_Label'] == 0)]
df_fn = df_fn.sort_values(by='Prediction_Prob', ascending=False)

print(f"Total False Negative ({chosen_id}): {len(df_fn)} emiten")
display(df_fn[['ticker', 'Year', 'True_Label', 'Predicted_Label', 'Prediction_Prob']].head(10))

# FP
df_fp = df_analyze[(df_analyze['True_Label'] == 0) & (df_analyze['Predicted_Label'] == 1)]
df_fp = df_fp.sort_values(by='Prediction_Prob', ascending=False)

print(f"\nTotal False Positive ({chosen_id}): {len(df_fp)} emiten")
display(df_fp[['ticker', 'Year', 'True_Label', 'Predicted_Label', 'Prediction_Prob']].head(10))
Total False Negative (ppk_keep_corr_CatBoost): 24 emiten
ticker Year True_Label Predicted_Label Prediction_Prob
150 CBUT 2023 1 0 0.009317
503 PYFA 2023 1 0 0.007977
330 KDSI 2023 1 0 0.007216
156 CITA 2023 1 0 0.006434
615 UANG 2023 1 0 0.006121
447 NRCA 2023 1 0 0.006096
84 BCIC 2023 1 0 0.005454
233 GDST 2023 1 0 0.005378
109 BMAS 2023 1 0 0.005265
467 PGUN 2023 1 0 0.004901

Total False Positive (ppk_keep_corr_CatBoost): 189 emiten
ticker Year True_Label Predicted_Label Prediction_Prob
224 FIRE 2023 0 1 0.274268
69 BAPA 2023 0 1 0.221099
501 PURI 2023 0 1 0.217655
489 PRIM 2023 0 1 0.203752
123 BRMS 2023 0 1 0.178601
148 CASH 2023 0 1 0.175944
180 DFAM 2023 0 1 0.173372
631 WAPO 2023 0 1 0.173192
450 OILS 2023 0 1 0.172687
436 NATO 2023 0 1 0.169980
detail = dict_detailed_predictions[chosen_id]

X_test = detail['X_test_features'].copy().fillna(np.nan).astype(float)
y_true = pd.Series(np.asarray(detail['y_true']), index=X_test.index)   # sesuaikan nama key label asli
y_pred = pd.Series(np.asarray(detail['y_pred']), index=X_test.index)   # sesuaikan nama key kelas akhir setelah threshold

# Kelompokkan menjadi TN / FP / FN / TP
group = np.where((y_true==0) & (y_pred==0), 'TN',
        np.where((y_true==0) & (y_pred==1), 'FP',
        np.where((y_true==1) & (y_pred==0), 'FN', 'TP')))
group = pd.Series(group, index=X_test.index, name='group')

# Bandingkan median rasio kunci antar kelompok (sesuaikan dengan fitur dominan tiap indikator)
key_ratios = ['ROE','NetMargin','ROA','Altman_X3_EBIT_TA','NetDebt_EBITDA',
              'Debt_TA','CR','log_MktCap','Sales_growth']
key_ratios = [c for c in key_ratios if c in X_test.columns]

summary = X_test[key_ratios].groupby(group).median().T
print(f"{chosen_id} :")
print(summary.to_csv())
print(group.value_counts().to_csv())   # jumlah tiap kelompok
ppk_keep_corr_CatBoost :
,FN,FP,TN,TP
ROE,0.0737876732128985,0.024918503599246248,0.09409720005512201,0.013487727488502679
NetMargin,0.08294989586052003,0.03154538916724116,0.10904804856248175,0.008568794964422415
Altman_X3_EBIT_TA,0.05608328409660638,0.029782958843770236,0.0808543970531425,0.01551875119097589
NetDebt_EBITDA,0.3697371800428746,1.6911939680814774,0.3010230064922693,0.6683013036180658
Debt_TA,0.13421265127934168,0.1863485439635218,0.14066510661028264,0.21197304602592348
CR,1.801,1.589,1.872,1.4845000000000002
log_MktCap,15.273112231471357,13.548624083703452,15.36946819508624,12.86321784992093
Sales_growth,-0.09335639346925134,0.09955861183635883,-0.0010163012901684976,0.002483406827388346

group,count
TN,256
FP,189
TP,182
FN,24

Analisis Sebaran Prediksi Distress Ekuitas Negatif

negeq_keep_all_RandomForest

chosen_id = 'negeq_keep_all_RandomForest'
data_pred = dict_detailed_predictions[chosen_id]

# gabung jadi 1 df
df_analyze = data_pred['metadata'].copy()
df_analyze['True_Label'] = data_pred['y_true']
df_analyze['Predicted_Label'] = data_pred['y_pred']
df_analyze['Prediction_Prob'] = data_pred['y_prob']
df_analyze = pd.concat([df_analyze, data_pred['X_test_features']], axis=1)

# FN
df_fn = df_analyze[(df_analyze['True_Label'] == 1) & (df_analyze['Predicted_Label'] == 0)]
df_fn = df_fn.sort_values(by='Prediction_Prob', ascending=False)

print(f"Total False Negative ({chosen_id}): {len(df_fn)} emiten")
display(df_fn[['ticker', 'Year', 'True_Label', 'Predicted_Label', 'Prediction_Prob']].head(10))

# FP
df_fp = df_analyze[(df_analyze['True_Label'] == 0) & (df_analyze['Predicted_Label'] == 1)]
df_fp = df_fp.sort_values(by='Prediction_Prob', ascending=False)

print(f"\nTotal False Positive ({chosen_id}): {len(df_fp)} emiten")
display(df_fp[['ticker', 'Year', 'True_Label', 'Predicted_Label', 'Prediction_Prob']].head(10))
Total False Negative (negeq_keep_all_RandomForest): 5 emiten
ticker Year True_Label Predicted_Label Prediction_Prob
238 INAF 2020 1 0 0.016615
1591 PBRX 2022 1 0 0.015860
811 INAF 2021 1 0 0.010583
1678 SINI 2022 1 0 0.006428
1050 SINI 2021 1 0 0.005528

Total False Positive (negeq_keep_all_RandomForest): 145 emiten
ticker Year True_Label Predicted_Label Prediction_Prob
1454 JAWA 2022 0 1 0.436186
356 MPPA 2020 0 1 0.410476
1552 MPPA 2022 0 1 0.399255
610 ATIC 2021 0 1 0.374170
1402 HELI 2022 0 1 0.370407
968 PDES 2021 0 1 0.358411
1603 PKPK 2022 0 1 0.344816
34 ARII 2020 0 1 0.344791
684 BULL 2021 0 1 0.344670
550 WSKT 2020 0 1 0.343143
detail = dict_detailed_predictions[chosen_id]

X_test = detail['X_test_features'].copy().fillna(np.nan).astype(float)
y_true = pd.Series(np.asarray(detail['y_true']), index=X_test.index)   # sesuaikan nama key label asli
y_pred = pd.Series(np.asarray(detail['y_pred']), index=X_test.index)   # sesuaikan nama key kelas akhir setelah threshold

# Kelompokkan menjadi TN / FP / FN / TP
group = np.where((y_true==0) & (y_pred==0), 'TN',
        np.where((y_true==0) & (y_pred==1), 'FP',
        np.where((y_true==1) & (y_pred==0), 'FN', 'TP')))
group = pd.Series(group, index=X_test.index, name='group')

# Bandingkan median rasio kunci antar kelompok (sesuaikan dengan fitur dominan tiap indikator)
key_ratios = ['ROE','NetMargin','ROA','Altman_X3_EBIT_TA','NetDebt_EBITDA',
              'Debt_TA','CR','log_MktCap','Sales_growth']
key_ratios = [c for c in key_ratios if c in X_test.columns]

summary = X_test[key_ratios].groupby(group).median().T
print(f"{chosen_id} :")
print(summary.to_csv())
print(group.value_counts().to_csv())   # jumlah tiap kelompok
negeq_keep_all_RandomForest :
,FN,FP,TN,TP
ROE,0.00655221648461087,-0.31780350980816624,0.04880420791817566,-1.2164644547600976
NetMargin,0.0033858418593652457,-0.21143220540852398,0.05248763223726913,-0.43555411960998003
ROA,0.003083477014625799,-0.08407876265522239,0.021723854675538962,-0.16809272696915364
Altman_X3_EBIT_TA,0.04413040869536313,-0.036905096394676984,0.04537896012869113,-0.07603731604254854
NetDebt_EBITDA,3.8983060533868716,-0.24730026745581324,0.9298757432311806,-1.8241009048130288
Debt_TA,0.3397132285383936,0.46540483566166735,0.1577316703887841,0.49102897766222076
CR,1.35,0.784,1.8295,0.547
log_MktCap,13.458343958881528,13.501860203904133,14.40421758037468,12.92067831698373
Sales_growth,0.2622269674201323,-0.16629620504781423,0.08129965435234521,-0.14904697345265228

group,count
TN,1609
FP,145
TP,29
FN,5

negeq_keep_corr_RandomForest

chosen_id = 'negeq_keep_corr_RandomForest'
data_pred = dict_detailed_predictions[chosen_id]

# gabung jadi 1 df
df_analyze = data_pred['metadata'].copy()
df_analyze['True_Label'] = data_pred['y_true']
df_analyze['Predicted_Label'] = data_pred['y_pred']
df_analyze['Prediction_Prob'] = data_pred['y_prob']
df_analyze = pd.concat([df_analyze, data_pred['X_test_features']], axis=1)

# FN
df_fn = df_analyze[(df_analyze['True_Label'] == 1) & (df_analyze['Predicted_Label'] == 0)]
df_fn = df_fn.sort_values(by='Prediction_Prob', ascending=False)

print(f"Total False Negative ({chosen_id}): {len(df_fn)} emiten")
display(df_fn[['ticker', 'Year', 'True_Label', 'Predicted_Label', 'Prediction_Prob']].head(10))

# FP
df_fp = df_analyze[(df_analyze['True_Label'] == 0) & (df_analyze['Predicted_Label'] == 1)]
df_fp = df_fp.sort_values(by='Prediction_Prob', ascending=False)

print(f"\nTotal False Positive ({chosen_id}): {len(df_fp)} emiten")
display(df_fp[['ticker', 'Year', 'True_Label', 'Predicted_Label', 'Prediction_Prob']].head(10))
Total False Negative (negeq_keep_corr_RandomForest): 5 emiten
ticker Year True_Label Predicted_Label Prediction_Prob
811 INAF 2021 1 0 0.018610
238 INAF 2020 1 0 0.006540
1678 SINI 2022 1 0 0.005010
1591 PBRX 2022 1 0 0.003880
1050 SINI 2021 1 0 0.000926

Total False Positive (negeq_keep_corr_RandomForest): 207 emiten
ticker Year True_Label Predicted_Label Prediction_Prob
1454 JAWA 2022 0 1 0.427964
1552 MPPA 2022 0 1 0.374975
610 ATIC 2021 0 1 0.360179
1603 PKPK 2022 0 1 0.326559
356 MPPA 2020 0 1 0.321963
1402 HELI 2022 0 1 0.313309
1000 PSDN 2021 0 1 0.302539
968 PDES 2021 0 1 0.299183
1624 PSDN 2022 0 1 0.298423
34 ARII 2020 0 1 0.285318
detail = dict_detailed_predictions[chosen_id]

X_test = detail['X_test_features'].copy().fillna(np.nan).astype(float)
y_true = pd.Series(np.asarray(detail['y_true']), index=X_test.index)   # sesuaikan nama key label asli
y_pred = pd.Series(np.asarray(detail['y_pred']), index=X_test.index)   # sesuaikan nama key kelas akhir setelah threshold

# Kelompokkan menjadi TN / FP / FN / TP
group = np.where((y_true==0) & (y_pred==0), 'TN',
        np.where((y_true==0) & (y_pred==1), 'FP',
        np.where((y_true==1) & (y_pred==0), 'FN', 'TP')))
group = pd.Series(group, index=X_test.index, name='group')

# Bandingkan median rasio kunci antar kelompok (sesuaikan dengan fitur dominan tiap indikator)
key_ratios = ['ROE','NetMargin','ROA','Altman_X3_EBIT_TA','NetDebt_EBITDA',
              'Debt_TA','CR','log_MktCap','Sales_growth']
key_ratios = [c for c in key_ratios if c in X_test.columns]

summary = X_test[key_ratios].groupby(group).median().T
print(f"{chosen_id} :")
print(summary.to_csv())
print(group.value_counts().to_csv())   # jumlah tiap kelompok
negeq_keep_corr_RandomForest :
,FN,FP,TN,TP
NetMargin,0.0033858418593652457,-0.18398862501132704,0.05542375368151475,-0.43555411960998003
ROA,0.003083477014625799,-0.07264230501754582,0.02418591882874731,-0.16809272696915364
Altman_X3_EBIT_TA,0.04413040869536313,-0.03836857971202155,0.04834008115888791,-0.07603731604254854
NetDebt_EBITDA,3.8983060533868716,1.481162864455563,0.8701563192272355,-1.8241009048130288
CR,1.35,0.8725,1.8495,0.547
log_MktCap,13.458343958881528,13.61608070123334,14.409578775981627,12.92067831698373
Sales_growth,0.2622269674201323,-0.16289992615988558,0.08651037088095137,-0.14904697345265228

group,count
TN,1547
FP,207
TP,29
FN,5

Analisis Sebaran Prediksi Distress Rugi Berturut-turut

conloss_keep_corr_CatBoost

chosen_id = 'conloss_keep_corr_CatBoost'
data_pred = dict_detailed_predictions[chosen_id]

# gabung jadi 1 df
df_analyze = data_pred['metadata'].copy()
df_analyze['True_Label'] = data_pred['y_true']
df_analyze['Predicted_Label'] = data_pred['y_pred']
df_analyze['Prediction_Prob'] = data_pred['y_prob']
df_analyze = pd.concat([df_analyze, data_pred['X_test_features']], axis=1)

# FN
df_fn = df_analyze[(df_analyze['True_Label'] == 1) & (df_analyze['Predicted_Label'] == 0)]
df_fn = df_fn.sort_values(by='Prediction_Prob', ascending=False)

print(f"Total False Negative ({chosen_id}): {len(df_fn)} emiten")
display(df_fn[['ticker', 'Year', 'True_Label', 'Predicted_Label', 'Prediction_Prob']].head(10))

# FP
df_fp = df_analyze[(df_analyze['True_Label'] == 0) & (df_analyze['Predicted_Label'] == 1)]
df_fp = df_fp.sort_values(by='Prediction_Prob', ascending=False)

print(f"\nTotal False Positive ({chosen_id}): {len(df_fp)} emiten")
display(df_fp[['ticker', 'Year', 'True_Label', 'Predicted_Label', 'Prediction_Prob']].head(10))
Total False Negative (conloss_keep_corr_CatBoost): 8 emiten
ticker Year True_Label Predicted_Label Prediction_Prob
132 FIRE 2020 1 0 0.057245
1093 PGLI 2022 1 0 0.041237
48 BBYB 2020 1 0 0.038582
655 NICK 2021 1 0 0.035563
164 IKBI 2020 1 0 0.027424
768 TPIA 2021 1 0 0.024245
1038 LUCY 2022 1 0 0.021578
986 INDR 2022 1 0 0.017618

Total False Positive (conloss_keep_corr_CatBoost): 338 emiten
ticker Year True_Label Predicted_Label Prediction_Prob
21 ARKA 2020 0 1 0.952964
767 TOYS 2021 0 1 0.921327
75 BOLT 2020 0 1 0.907221
948 FLMC 2022 0 1 0.886973
1081 NPGF 2022 0 1 0.879342
418 AMIN 2021 0 1 0.865227
311 RISE 2020 0 1 0.842084
771 TRIN 2021 0 1 0.826556
240 MGRO 2020 0 1 0.821270
207 KKGI 2020 0 1 0.797002
detail = dict_detailed_predictions[chosen_id]

X_test = detail['X_test_features'].copy().fillna(np.nan).astype(float)
y_true = pd.Series(np.asarray(detail['y_true']), index=X_test.index)   # sesuaikan nama key label asli
y_pred = pd.Series(np.asarray(detail['y_pred']), index=X_test.index)   # sesuaikan nama key kelas akhir setelah threshold

# Kelompokkan menjadi TN / FP / FN / TP
group = np.where((y_true==0) & (y_pred==0), 'TN',
        np.where((y_true==0) & (y_pred==1), 'FP',
        np.where((y_true==1) & (y_pred==0), 'FN', 'TP')))
group = pd.Series(group, index=X_test.index, name='group')

# Bandingkan median rasio kunci antar kelompok (sesuaikan dengan fitur dominan tiap indikator)
key_ratios = ['ROE','NetMargin','ROA','Altman_X3_EBIT_TA','NetDebt_EBITDA',
              'Debt_TA','CR','log_MktCap','Sales_growth']
key_ratios = [c for c in key_ratios if c in X_test.columns]

summary = X_test[key_ratios].groupby(group).median().T
print(f"{chosen_id} :")
print(summary.to_csv())
print(group.value_counts().to_csv())   # jumlah tiap kelompok
conloss_keep_corr_CatBoost :
,FN,FP,TN,TP
ROE,0.06973346472475316,0.013818175197601413,0.10642121740162772,-0.021012988674344306
NetMargin,0.06561682167705439,0.022098578640617778,0.12129330947292344,-0.053001829489680956
ROA,0.03865848800501101,0.006478717218521261,0.05761557462599317,-0.013737448470650032
Altman_X3_EBIT_TA,0.061140744489493616,0.020776397229527455,0.09543227819932194,-0.006566356588855474
NetDebt_EBITDA,-0.6522820015736247,3.0126958621639,0.12739919640942127,3.431945781488151
Debt_TA,0.1625305941549952,0.18870753274333193,0.11710562177318196,0.24667656877397628
CR,2.641,1.62,2.0084999999999997,1.439
log_MktCap,13.789160423497503,13.951596122757346,15.176876458371567,13.333281658216443
Sales_growth,-0.003497763784430008,-0.04027502829524687,0.12257603914504323,-0.09611319659319512

group,count
TN,731
FP,338
TP,150
FN,8

conloss_keep_all_XGBoost

chosen_id = 'conloss_keep_all_XGBoost'
data_pred = dict_detailed_predictions[chosen_id]

# gabung jadi 1 df
df_analyze = data_pred['metadata'].copy()
df_analyze['True_Label'] = data_pred['y_true']
df_analyze['Predicted_Label'] = data_pred['y_pred']
df_analyze['Prediction_Prob'] = data_pred['y_prob']
df_analyze = pd.concat([df_analyze, data_pred['X_test_features']], axis=1)

# FN
df_fn = df_analyze[(df_analyze['True_Label'] == 1) & (df_analyze['Predicted_Label'] == 0)]
df_fn = df_fn.sort_values(by='Prediction_Prob', ascending=False)

print(f"Total False Negative ({chosen_id}): {len(df_fn)} emiten")
display(df_fn[['ticker', 'Year', 'True_Label', 'Predicted_Label', 'Prediction_Prob']].head(10))

# FP
df_fp = df_analyze[(df_analyze['True_Label'] == 0) & (df_analyze['Predicted_Label'] == 1)]
df_fp = df_fp.sort_values(by='Prediction_Prob', ascending=False)

print(f"\nTotal False Positive ({chosen_id}): {len(df_fp)} emiten")
display(df_fp[['ticker', 'Year', 'True_Label', 'Predicted_Label', 'Prediction_Prob']].head(10))
Total False Negative (conloss_keep_all_XGBoost): 9 emiten
ticker Year True_Label Predicted_Label Prediction_Prob
132 FIRE 2020 1 0 0.058509
1181 TGRA 2022 1 0 0.056347
1011 KBAG 2022 1 0 0.051167
48 BBYB 2020 1 0 0.043425
655 NICK 2021 1 0 0.043004
1038 LUCY 2022 1 0 0.036423
164 IKBI 2020 1 0 0.033780
986 INDR 2022 1 0 0.017504
768 TPIA 2021 1 0 0.013567

Total False Positive (conloss_keep_all_XGBoost): 349 emiten
ticker Year True_Label Predicted_Label Prediction_Prob
21 ARKA 2020 0 1 0.907378
767 TOYS 2021 0 1 0.875710
771 TRIN 2021 0 1 0.849894
1081 NPGF 2022 0 1 0.837772
131 FILM 2020 0 1 0.834883
400 ZONE 2020 0 1 0.827859
75 BOLT 2020 0 1 0.814995
128 ESSA 2020 0 1 0.813899
418 AMIN 2021 0 1 0.805717
965 HELI 2022 0 1 0.805020
detail = dict_detailed_predictions[chosen_id]

X_test = detail['X_test_features'].copy().fillna(np.nan).astype(float)
y_true = pd.Series(np.asarray(detail['y_true']), index=X_test.index)   # sesuaikan nama key label asli
y_pred = pd.Series(np.asarray(detail['y_pred']), index=X_test.index)   # sesuaikan nama key kelas akhir setelah threshold

# Kelompokkan menjadi TN / FP / FN / TP
group = np.where((y_true==0) & (y_pred==0), 'TN',
        np.where((y_true==0) & (y_pred==1), 'FP',
        np.where((y_true==1) & (y_pred==0), 'FN', 'TP')))
group = pd.Series(group, index=X_test.index, name='group')

# Bandingkan median rasio kunci antar kelompok (sesuaikan dengan fitur dominan tiap indikator)
key_ratios = ['ROE','NetMargin','ROA','Altman_X3_EBIT_TA','NetDebt_EBITDA',
              'Debt_TA','CR','log_MktCap','Sales_growth']
key_ratios = [c for c in key_ratios if c in X_test.columns]

summary = X_test[key_ratios].groupby(group).median().T
print(f"{chosen_id} :")
print(summary.to_csv())
print(group.value_counts().to_csv())   # jumlah tiap kelompok
conloss_keep_all_XGBoost :
,FN,FP,TN,TP
ROE,0.0391288162018532,0.01557552316677661,0.1071394545586764,-0.021598812200488975
NetMargin,0.07237938485018018,0.02241751236404642,0.12475275948013481,-0.05337829003929229
ROA,0.027331009244866512,0.007876164183510045,0.0584392967320556,-0.013772726153386183
Altman_X3_EBIT_TA,0.05657245925955019,0.02343737441022285,0.09704772804843081,-0.0075738046907378054
NetDebt_EBITDA,0.5056435143553407,2.9515924835737026,0.08390103135401727,2.920838191339234
Debt_TA,0.1513808856078739,0.21723667543184766,0.11209597726655329,0.25099671833206216
CR,2.402,1.6955,1.999,1.47
log_MktCap,13.098813236946116,13.926523435169825,15.221189484207997,13.384727641873358
Sales_growth,-0.0311681686020146,-0.04316301141169804,0.12456022216722484,-0.08736368319417454

group,count
TN,720
FP,349
TP,149
FN,9

Rancangan Deployment

Deployment

!pip install session_info -q
import session_info
session_info.show()
Click to view session information
-----
catboost            1.2.10
google              NA
imblearn            0.14.2
lightgbm            4.6.0
matplotlib          3.10.0
numpy               2.0.2
optuna              4.9.0
pandas              2.2.2
plotly              5.24.1
scipy               1.16.3
seaborn             0.13.2
session_info        v1.0.1
shap                0.52.0
sklearn             1.6.1
statsmodels         0.14.6
xgboost             3.2.0
-----
Click to view modules imported as dependencies
81d243bd2c585b0f4821__mypyc NA
Cython                      3.0.12
PIL                         11.3.0
ac51d50a4f4b6d748b8c__mypyc NA
anywidget                   0.9.21
arrow                       1.4.0
attr                        26.1.0
attrs                       26.1.0
backcall                    0.2.0
bottleneck                  1.4.2
certifi                     2026.05.20
cffi                        2.0.0
charset_normalizer          3.4.7
cloudpickle                 3.1.2
colorlog                    NA
cramjam                     2.11.0
cv2                         4.13.0
cvxopt                      1.3.2
cycler                      0.12.1
cython                      3.0.12
cython_runtime              NA
dask                        2026.3.0
dateutil                    2.9.0.post0
debugpy                     1.8.15
decorator                   4.4.2
defusedxml                  0.7.1
entrypoints                 0.4
fastjsonschema              NA
fqdn                        NA
fsspec                      2025.3.0
graphviz                    0.21
httplib2                    0.31.2
idna                        3.18
iniconfig                   NA
ipykernel                   6.17.1
ipython_genutils            0.2.0
ipywidgets                  7.7.1
isoduration                 NA
jinja2                      3.1.6
joblib                      1.5.3
jsonpointer                 3.1.1
jsonschema                  4.26.0
jsonschema_specifications   NA
kaleido                     0.2.1
kiwisolver                  1.5.0
lark                        1.3.1
llvmlite                    0.43.0
lxml                        6.1.1
markupsafe                  3.0.3
matplotlib_inline           0.2.2
mmh3                        NA
mpl_toolkits                NA
nbformat                    5.10.4
numba                       0.60.0
numexpr                     2.14.1
openpyxl                    3.1.5
orjson                      3.11.9
packaging                   26.2
patsy                       1.0.2
pexpect                     4.9.0
pickleshare                 0.7.5
platformdirs                4.10.0
pluggy                      1.6.0
polars                      1.35.2
prompt_toolkit              3.0.52
psutil                      5.9.5
psygnal                     0.15.1
ptyprocess                  0.7.0
py                          NA
py4j                        0.10.9.9
pyarrow                     18.1.0
pycparser                   3.00
pydev_ipython               NA
pydevconsole                NA
pydevd                      3.2.3
pydevd_file_utils           NA
pydevd_plugins              NA
pydevd_tracing              NA
pygments                    2.20.0
pyparsing                   3.3.2
pyspark                     4.0.2
pytest                      8.4.2
pytz                        2025.2
referencing                 NA
regex                       2025.11.3
rfc3339_validator           0.1.4
rfc3986_validator           0.1.1
rfc3987_syntax              NA
rpds                        NA
sitecustomize               NA
six                         1.17.0
sklearn_compat              0.1.6
slicer                      NA
snappy                      0.7.3
socks                       1.7.1
sphinxcontrib               NA
storemagic                  NA
tenacity                    NA
threadpoolctl               3.6.0
tlz                         0.12.1
toolz                       0.12.1
tornado                     6.5.7
tqdm                        4.67.3
traitlets                   5.7.1
typing_extensions           NA
uri_template                NA
vscode                      NA
wcwidth                     0.8.1
webcolors                   NA
xarray                      2025.12.0
xlrd                        2.0.2
xxhash                      NA
yaml                        6.0.3
zmq                         26.2.1
zoneinfo                    NA
zstandard                   0.25.0
-----
IPython             7.34.0
jupyter_client      7.4.9
jupyter_core        5.9.1
notebook            6.5.7
-----
Python 3.12.13 (main, Mar  4 2026, 09:23:07) [GCC 11.4.0]
Linux-6.6.122+-x86_64-with-glibc2.35
-----
Session information updated at 2026-06-13 14:15