library(DiagrammeR)
library(seminr)
library(tidyverse)
library(skimr)
library(corrplot)
library(kableExtra) if (!file.exists('Combined_Datalist_v1.1.csv')){
print("Dataset tidak ditemukan...")
print("Memulai Download Dataset...")
download.file('https://drive.google.com/uc?id=1QEaHEoS_Od4N56U_JTlduEKRaPSXvxi8', 'Combined_Datalist_v1.1.csv', mode = 'wb')
}
# Load dataset
df_raw <- read.csv("Combined_Datalist_v1.1.csv", sep= ";", stringsAsFactors = TRUE)
print("Dataset selesai di Donwload")## [1] "Dataset selesai di Donwload"
## Dimensi data: 8980 baris x 18 kolom
## [1] "Kecamatan" "Price" "Kamar.Tidur"
## [4] "Kamar.Mandi" "Luas.Tanah" "Luas.Bangunan"
## [7] "Sertifikat" "Daya.Listrik" "Ruang.Makan"
## [10] "Ruang.Tamu" "Kondisi.Perabotan" "Jumlah.Lantai"
## [13] "Hadap" "Terjangkau.Internet" "Lebar.Jalan"
## [16] "Sumber.Air" "Hook" "Kondisi.Properti"
# Bersihkan nama kolom (hapus spasi)
df <- df_raw
colnames(df) <- trimws(colnames(df))
# Bersihkan dan konversi kolom Price
# Format asli: " 600.000.000 " -> 600000000
df$Price <- df$Price %>%
trimws() %>%
gsub("\\.", "", .) %>% # hapus titik ribuan
as.numeric()
# Cek distribusi Price (dalam juta rupiah untuk readability)
cat("Price (juta Rp) - Min:", min(df$Price, na.rm=TRUE)/1e6,
"| Max:", max(df$Price, na.rm=TRUE)/1e6,
"| Median:", median(df$Price, na.rm=TRUE)/1e6, "\n")## Price (juta Rp) - Min: 600 | Max: 85000 | Median: 2200
#Variabel Biner (Ya=1, Tidak=0)
binary_vars <- c("Ruang.Makan", "Ruang.Tamu", "Terjangkau.Internet", "Hook")
# Cek nama kolom aktual
colnames(df) <- gsub(" ", ".", colnames(df))
df <- df %>%
mutate(
# Biner
Ruang.Makan = ifelse(Ruang.Makan == "Ya", 1, 0),
Ruang.Tamu = ifelse(Ruang.Tamu == "Ya", 1, 0),
Terjangkau.Internet = ifelse(Terjangkau.Internet == "Ya", 1, 0),
Hook = ifelse(Hook == "Ya", 1, 0),
# Kondisi Perabotan (ordinal: Unfurnished=0, Semi Furnished=1)
Kondisi.Perabotan = case_when(
Kondisi.Perabotan == "Unfurnished" ~ 0,
Kondisi.Perabotan == "Semi Furnished" ~ 1,
TRUE ~ NA_real_
),
# Kondisi Properti (ordinal: semakin baik = nilai lebih tinggi)
Kondisi.Properti = case_when(
Kondisi.Properti == "Butuh Renovasi" ~ 1,
Kondisi.Properti == "Sudah Renovasi" ~ 2,
Kondisi.Properti == "Bagus" ~ 3,
Kondisi.Properti == "Bagus Sekali" ~ 4,
Kondisi.Properti == "Baru" ~ 5,
TRUE ~ NA_real_
),
# Sertifikat (ordinal berdasarkan kekuatan hukum)
Sertifikat = case_when(
grepl("Lainnya|PPJB|Girik|Adat", Sertifikat) ~ 1,
grepl("HP|Hak Pakai", Sertifikat) ~ 2,
grepl("HGB|Hak Guna", Sertifikat) ~ 3,
grepl("SHM|Hak Milik", Sertifikat) ~ 4,
TRUE ~ NA_real_
)
)
cat("Encoding selesai.\n")## Encoding selesai.
# Kolom yang masuk di pake
cols_model <- c(
# Ukuran Fisik
"Luas.Tanah", "Luas.Bangunan", "Kamar.Tidur", "Kamar.Mandi", "Jumlah.Lantai",
# Fasilitas Interior
"Ruang.Makan", "Ruang.Tamu", "Daya.Listrik",
# Kualitas & Legalitas
"Kondisi.Properti", "Sertifikat",
# Outcome
"Price"
)
df_model <- df %>%
select(all_of(cols_model)) %>%
drop_na()
cat("Jumlah observasi setelah drop NA:", nrow(df_model), "\n")## Jumlah observasi setelah drop NA: 6444
## Kolom yang digunakan: 11
df_model %>%
skim() %>%
kable(caption = "Statistik Deskriptif Variabel Model") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)| skim_type | skim_variable | n_missing | complete_rate | numeric.mean | numeric.sd | numeric.p0 | numeric.p25 | numeric.p50 | numeric.p75 | numeric.p100 | numeric.hist |
|---|---|---|---|---|---|---|---|---|---|---|---|
| numeric | Luas.Tanah | 0 | 1 | 1.780357e+02 | 1.315130e+02 | 4.5e+01 | 9.6e+01 | 1.33e+02 | 2.1e+02 | 1.00e+03 | ▇▂▁▁▁ |
| numeric | Luas.Bangunan | 0 | 1 | 1.969064e+02 | 1.455973e+02 | 5.0e+01 | 1.1e+02 | 1.55e+02 | 2.3e+02 | 3.30e+03 | ▇▁▁▁▁ |
| numeric | Kamar.Tidur | 0 | 1 | 3.539882e+00 | 1.042385e+00 | 1.0e+00 | 3.0e+00 | 3.00e+00 | 4.0e+00 | 7.00e+00 | ▂▇▆▂▁ |
| numeric | Kamar.Mandi | 0 | 1 | 2.670081e+00 | 1.018119e+00 | 1.0e+00 | 2.0e+00 | 3.00e+00 | 3.0e+00 | 7.00e+00 | ▇▇▂▁▁ |
| numeric | Jumlah.Lantai | 0 | 1 | 1.670081e+00 | 5.298174e-01 | 1.0e+00 | 1.0e+00 | 2.00e+00 | 2.0e+00 | 4.00e+00 | ▅▇▁▁▁ |
| numeric | Ruang.Makan | 0 | 1 | 4.442893e-01 | 4.969252e-01 | 0.0e+00 | 0.0e+00 | 0.00e+00 | 1.0e+00 | 1.00e+00 | ▇▁▁▁▆ |
| numeric | Ruang.Tamu | 0 | 1 | 5.681254e-01 | 4.953756e-01 | 0.0e+00 | 0.0e+00 | 1.00e+00 | 1.0e+00 | 1.00e+00 | ▆▁▁▁▇ |
| numeric | Daya.Listrik | 0 | 1 | 2.991589e+03 | 1.747596e+03 | 9.0e+02 | 2.2e+03 | 2.20e+03 | 3.5e+03 | 1.76e+04 | ▇▂▁▁▁ |
| numeric | Kondisi.Properti | 0 | 1 | 3.573246e+00 | 1.149025e+00 | 1.0e+00 | 3.0e+00 | 3.00e+00 | 5.0e+00 | 5.00e+00 | ▁▁▇▁▅ |
| numeric | Sertifikat | 0 | 1 | 3.719429e+00 | 6.698222e-01 | 1.0e+00 | 4.0e+00 | 4.00e+00 | 4.0e+00 | 4.00e+00 | ▁▁▁▂▇ |
| numeric | Price | 0 | 1 | 3.186012e+09 | 3.637670e+09 | 6.0e+08 | 1.5e+09 | 2.10e+09 | 3.5e+09 | 8.50e+10 | ▇▁▁▁▁ |
df_model %>%
mutate(Price_juta = Price / 1e6) %>%
ggplot(aes(x = Price_juta)) +
geom_histogram(bins = 50, fill = "#2E86AB", color = "white", alpha = 0.8) +
scale_x_continuous(labels = scales::comma) +
labs(
title = "Distribusi Harga Properti di Surabaya",
x = "Harga (Juta Rupiah)",
y = "Frekuensi"
) +
theme_minimal()Di sini kita mendefinisikan konstruk laten beserta
indikatornya. Karena data bersifat formatif (indikator
membentuk konstruk), kita gunakan
composite("nama", all_items("..."), weights = mode_B).
Catatan:
mode_Badalah mode PLS untuk model formatif, sedangkanmode_Auntuk reflektif.
measurements <- constructs(
composite("Ukuran_Fisik",
multi_items("", c("Luas.Tanah", "Luas.Bangunan",
"Kamar.Tidur", "Kamar.Mandi",
"Jumlah.Lantai")),
weights = mode_B),
composite("Fasilitas_Interior",
multi_items("", c("Ruang.Makan", "Ruang.Tamu",
"Daya.Listrik")),
weights = mode_B),
composite("Kualitas_Legalitas",
multi_items("", c("Kondisi.Properti", "Sertifikat")),
weights = mode_B),
composite("Harga",
single_item("Price"))
)Di sini kita mendefinisikan path (jalur) antar konstruk laten — mana yang mempengaruhi mana.
# Estimasi PLS-SEM
pls_model <- estimate_pls(
data = as.data.frame(df_model),
measurement_model = measurements,
structural_model = structure,
inner_weights = path_weighting # default: path weighting algorithm
)
# Ringkasan model
summary_pls <- summary(pls_model)
cat("Model berhasil diestimasi.\n")## Model berhasil diestimasi.
Untuk model formatif, evaluasi fokus pada:
## OUTER WEIGHTS
## Ukuran_Fisik Fasilitas_Interior Kualitas_Legalitas Harga
## Luas.Tanah 0.840 0.000 0.000 0.000
## Luas.Bangunan 0.219 0.000 0.000 0.000
## Kamar.Tidur -0.088 0.000 0.000 0.000
## Kamar.Mandi 0.083 0.000 0.000 0.000
## Jumlah.Lantai 0.108 0.000 0.000 0.000
## Ruang.Makan 0.000 0.016 0.000 0.000
## Ruang.Tamu 0.000 -0.013 0.000 0.000
## Daya.Listrik 0.000 1.000 0.000 0.000
## Kondisi.Properti 0.000 0.000 0.992 0.000
## Sertifikat 0.000 0.000 0.205 0.000
## Price 0.000 0.000 0.000 1.000
## VIF INDIKATOR (Formatif)
## Ukuran_Fisik :
## Luas.Tanah Luas.Bangunan Kamar.Tidur Kamar.Mandi Jumlah.Lantai
## 2.387 2.640 1.688 1.854 1.295
##
## Fasilitas_Interior :
## Ruang.Makan Ruang.Tamu Daya.Listrik
## 2.330 2.326 1.003
##
## Kualitas_Legalitas :
## Kondisi.Properti Sertifikat
## 1.004 1.004
##
## Harga :
## Price
## 1
##
## Catatan: VIF < 5 = tidak ada masalah multikolinearitas serius
## VIF < 3.3 = ideal untuk model formatif
Evaluasi inner model fokus pada:
## R-SQUARED
## Harga
## R^2 0.567
## AdjR^2 0.567
## Ukuran_Fisik 0.695
## Fasilitas_Interior 0.116
## Kualitas_Legalitas 0.059
## Interpretasi R²
## 0.19 = lemah | 0.33 = moderat | 0.67 = kuat (Hair et al., 2019)
## PATH COEFFICIENTS
## Harga
## R^2 0.567
## AdjR^2 0.567
## Ukuran_Fisik 0.695
## Fasilitas_Interior 0.116
## Kualitas_Legalitas 0.059
Bootstrapping digunakan karena PLS-SEM tidak mengasumsikan distribusi normal — kita tidak bisa pakai p-value dari distribusi t biasa.
# Bootstrapping (1000 resample) untuk inferensi statistik
set.seed(123)
boot_model <- bootstrap_model(
seminr_model = pls_model,
nboot = 1000,
cores = 1
)
summary_boot <- summary(boot_model, alpha = 0.05)
cat("HASIL BOOTSTRAPPING: PATH COEFFICIENTS ")## HASIL BOOTSTRAPPING: PATH COEFFICIENTS
## Original Est. Bootstrap Mean Bootstrap SD T Stat.
## Ukuran_Fisik -> Harga 0.695 0.696 0.019 36.926
## Fasilitas_Interior -> Harga 0.116 0.116 0.020 5.735
## Kualitas_Legalitas -> Harga 0.059 0.058 0.009 6.376
## 2.5% CI 97.5% CI Bootstrap P Val
## Ukuran_Fisik -> Harga 0.661 0.732 0.000
## Fasilitas_Interior -> Harga 0.076 0.155 0.000
## Kualitas_Legalitas -> Harga 0.039 0.076 0.000
##
## Kolom penting:
## - 'Original Est.' : koefisien jalur dari model asli
## - 'Bootstrap Mean': rata-rata dari 1000 bootstrap sample
## - 'T Stat.' : t-statistic (|T| > 1.96 = signifikan pada α=0.05)
## - '2.5% CI' & '97.5% CI': confidence interval
# Buat tabel ringkasan hipotesis manual berdasarkan hasil bootstrap
hipotesis <- data.frame(
Hipotesis = c("H1", "H2", "H3"),
Path = c(
"Ukuran Fisik → Harga",
"Fasilitas Interior → Harga",
"Kualitas & Legalitas → Harga"
),
`Path Coefficient` = c(
round(summary_pls$paths["Ukuran_Fisik", "Harga"], 3),
round(summary_pls$paths["Fasilitas_Interior", "Harga"], 3),
round(summary_pls$paths["Kualitas_Legalitas", "Harga"], 3)
),
check.names = FALSE
)
hipotesis %>%
kable(caption = "Ringkasan Pengujian Hipotesis") %>%
kable_styling(bootstrap_options = c("striped", "hover"),
full_width = FALSE) %>%
column_spec(1, bold = TRUE)| Hipotesis | Path | Path Coefficient |
|---|---|---|
| H1 | Ukuran Fisik → Harga | 0.695 |
| H2 | Fasilitas Interior → Harga | 0.116 |
| H3 | Kualitas & Legalitas → Harga | 0.059 |
# Visualisasi koefisien jalur
path_df <- data.frame(
Konstruk = c("Ukuran Fisik", "Fasilitas Interior", "Kualitas & Legalitas"),
Koefisien = c(
summary_pls$paths["Ukuran_Fisik", "Harga"],
summary_pls$paths["Fasilitas_Interior", "Harga"],
summary_pls$paths["Kualitas_Legalitas", "Harga"]
)
)
ggplot(path_df, aes(x = reorder(Konstruk, Koefisien), y = Koefisien, fill = Koefisien > 0)) +
geom_col(width = 0.5, show.legend = FALSE) +
geom_hline(yintercept = 0, linetype = "dashed", color = "gray40") +
scale_fill_manual(values = c("TRUE" = "#2E86AB", "FALSE" = "#E84855")) +
coord_flip() +
labs(
title = "Path Coefficients: Pengaruh terhadap Harga Properti",
x = NULL,
y = "Path Coefficient"
) +
theme_minimal(base_size = 12)grViz("
digraph PLS_SEM {
graph [rankdir = LR, fontname = 'Helvetica']
node [fontname = 'Helvetica', fontsize = 10, shape = rectangle, style = filled]
edge [fontname = 'Helvetica', fontsize = 9]
# Indikator - Ukuran Fisik
node [fillcolor = '#9FE1CB', color = '#0F6E56']
LT [label = 'Luas Tanah']
LB [label = 'Luas Bangunan']
KT [label = 'Kamar Tidur']
KM [label = 'Kamar Mandi']
JL [label = 'Jumlah Lantai']
# Indikator - Fasilitas Interior
RM [label = 'Ruang Makan']
RTa [label = 'Ruang Tamu']
DL [label = 'Daya Listrik']
# Indikator - Kualitas & Legalitas
KP [label = 'Kondisi Properti']
S [label = 'Sertifikat']
# Indikator - Harga
node [shape = rectangle]
PR [label = 'Price (Rp)']
# Konstruk Laten
node [fillcolor = '#CECBF6', color = '#3C3489', shape = ellipse, fontsize = 11]
UF [label = 'Ukuran\\nFisik']
FI [label = 'Fasilitas\\nInterior']
KL [label = 'Kualitas &\\nLegalitas']
node [fillcolor = '#F5C4B3', color = '#993C1D']
H [label = 'Harga\\n(R² = 0.567)']
# Outer model: indikator → konstruk (formatif)
LT -> UF; LB -> UF; KT -> UF; KM -> UF; JL -> UF
RM -> FI; RTa -> FI; DL -> FI
KP -> KL; S -> KL
PR -> H
# Inner model: konstruk → Harga
UF -> H [label = 'β = 0.695', penwidth = 4, color = '#534AB7']
FI -> H [label = 'β = 0.116', penwidth = 1.8, color = '#534AB7']
KL -> H [label = 'β = 0.059', penwidth = 1, color = '#534AB7']
# Ranking node (kiri ke kanan)
{ rank = same; LT; LB; KT; KM; JL }
{ rank = same; RM; RTa; DL }
{ rank = same; KP; S }
}
")Berdasarkan hasil analisis PLS-SEM dengan menggunakan data properti Surabaya (n = 6444 observasi), diperoleh temuan sebagai berikut:
Ukuran Fisik (luas tanah, luas bangunan, kamar tidur, kamar mandi, jumlah lantai) terbukti berpengaruh positif dan signifikan terhadap harga properti (β = 0.695, t = 36.926, p < 0.05). H1 diterima.
Fasilitas Interior (ruang makan, ruang tamu, daya listrik) terbukti berpengaruh positif dan signifikan terhadap harga properti (β = 0.116, t = 5.735, p < 0.05). H2 diterima.
Kualitas & Legalitas (kondisi properti, sertifikat) terbukti berpengaruh positif dan signifikan terhadap harga properti (β = 0.059, t = 6.376, p < 0.05). H3 diterima.
Nilai R² = 0.567 menunjukkan bahwa model mampu menjelaskan 56.7% variasi harga properti di Surabaya, yang tergolong moderat-kuat menurut kriteria Hair et al. (2022) dengan threshold 0.33 (moderat) dan 0.67 (kuat).