Topik ini menuntaskan pengerjaan kueri rekomendasi yang telah dirancang sebelumnya dan menghubungkannya ke sebuah aplikasi Python melalui driver resmi Neo4j, yaitu pustaka yang menerjemahkan panggilan program menjadi protokol Bolt. Kueri dua lompatan dengan skor konsumsi bersama ditulis dan diuji terlebih dahulu di cypher-shell, kemudian dipindahkan ke berkas rekomendasi.py yang mengambil hasilnya dari dalam program. Koneksi menggunakan alamat bolt://localhost:7687, sandi dibaca dari variabel lingkungan, dan nilai yang berubah dikirim sebagai parameter kueri. Setelah topik ini diikuti, rekomendasi untuk satu pengguna dapat diambil dari dalam kode aplikasi dengan urutan dan batas jumlah yang ditetapkan kuerinya sendiri.
Data roster pembelajaran#
Kueri rekomendasi pada topik ini bekerja atas roster pembelajaran modul, yaitu node berlabel :Pengguna dan :Kursus yang dihubungkan relasi :MENGIKUTI dari pengguna ke kursus yang diikutinya. Setiap node :Pengguna menyimpan properti nama, setiap node :Kursus menyimpan properti judul, dan setiap relasi membawa properti progres berupa bilangan bulat 0 sampai 100. Roster sengaja dibuat kecil agar setiap skor masih dapat dihitung ulang secara manual, dan progres tidak digunakan sebagai pembobot pada topik ini karena skor rekomendasi dihitung dari banyaknya baris pola, bukan dari nilai tersimpan.
Susunan relasinya ditetapkan sebagai berikut. Hesti mengikuti Pengantar Algoritma, Basis Data Graf, Pemrograman Python, dan Rekayasa Perangkat Lunak. Indra mengikuti Pengantar Algoritma, Pemrograman Python, dan Statistika Terapan, sedangkan Joko mengikuti Basis Data Graf dan Jaringan Komputer. Kartika mengikuti Pengantar Algoritma, Basis Data Graf, Pemrograman Python, dan Jaringan Komputer, sedangkan Laila mengikuti Statistika Terapan, Pemrograman Python, dan Jaringan Komputer. Maya melengkapi roster dengan mengikuti Basis Data Graf, Statistika Terapan, dan Rekayasa Perangkat Lunak.
Hesti dan Kartika berbagi tiga kursus, yaitu Pengantar Algoritma, Basis Data Graf, dan Pemrograman Python, sedangkan pasangan lainnya berbagi paling banyak dua kursus. Skrip setup.cypher berikut membangun ulang seluruh roster dengan MERGE, sehingga aman dijalankan berulang tanpa menduplikasi node maupun relasi. Karena instance latihan digunakan bersama oleh dataset dari beberapa topik, pembersihan di awal skrip menghapus hanya nama-nama milik modul ini sebelum roster dibangun kembali. Cara memuatnya adalah mengalirkan berkas tersebut ke cypher-shell melalui pipa, atau mengeksekusinya dari Neo4j Browser.
// Dataset roster pembelajaran untuk topik Membangun mesin rekomendasi.
// Roster sama dengan dataset modul pada topik Di mana graf menang dan kalah,
// yaitu enam pengguna, enam kursus, dan relasi MENGIKUTI yang membawa properti
// progres berupa bilangan bulat 0-100.
// Instance latihan dipakai bersama oleh dataset dari beberapa topik, sehingga
// pembersihan di awal hanya menyentuh nama-nama milik modul ini.
// Skrip ini idempoten (idempotent), artinya aman dijalankan berulang kali.
// Pembersihan terbatas pada nama-nama milik modul ini
MATCH (p:Pengguna)
WHERE p.nama IN ["Hesti", "Indra", "Joko", "Kartika", "Laila", "Maya"]
DETACH DELETE p;
MATCH (k:Kursus)
WHERE k.judul IN ["Pengantar Algoritma", "Basis Data Graf", "Statistika Terapan",
"Pemrograman Python", "Jaringan Komputer", "Rekayasa Perangkat Lunak"]
DETACH DELETE k;
// Node pengguna: label :Pengguna dengan properti nama.
MERGE (:Pengguna {nama: "Hesti"});
MERGE (:Pengguna {nama: "Indra"});
MERGE (:Pengguna {nama: "Joko"});
MERGE (:Pengguna {nama: "Kartika"});
MERGE (:Pengguna {nama: "Laila"});
MERGE (:Pengguna {nama: "Maya"});
// Node kursus: label :Kursus dengan properti judul.
MERGE (:Kursus {judul: "Pengantar Algoritma"});
MERGE (:Kursus {judul: "Basis Data Graf"});
MERGE (:Kursus {judul: "Statistika Terapan"});
MERGE (:Kursus {judul: "Pemrograman Python"});
MERGE (:Kursus {judul: "Jaringan Komputer"});
MERGE (:Kursus {judul: "Rekayasa Perangkat Lunak"});
// Relasi MENGIKUTI berarah dari pengguna menuju kursus, satu relasi per
// pasangan pengguna dan kursus, dengan properti progres.
MATCH (p:Pengguna {nama: "Hesti"}), (k:Kursus {judul: "Pengantar Algoritma"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 80;
MATCH (p:Pengguna {nama: "Hesti"}), (k:Kursus {judul: "Basis Data Graf"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 65;
MATCH (p:Pengguna {nama: "Hesti"}), (k:Kursus {judul: "Pemrograman Python"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 90;
MATCH (p:Pengguna {nama: "Hesti"}), (k:Kursus {judul: "Rekayasa Perangkat Lunak"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 40;
MATCH (p:Pengguna {nama: "Indra"}), (k:Kursus {judul: "Pengantar Algoritma"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 75;
MATCH (p:Pengguna {nama: "Indra"}), (k:Kursus {judul: "Pemrograman Python"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 60;
MATCH (p:Pengguna {nama: "Indra"}), (k:Kursus {judul: "Statistika Terapan"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 45;
MATCH (p:Pengguna {nama: "Joko"}), (k:Kursus {judul: "Basis Data Graf"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 55;
MATCH (p:Pengguna {nama: "Joko"}), (k:Kursus {judul: "Jaringan Komputer"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 70;
MATCH (p:Pengguna {nama: "Kartika"}), (k:Kursus {judul: "Pengantar Algoritma"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 70;
MATCH (p:Pengguna {nama: "Kartika"}), (k:Kursus {judul: "Basis Data Graf"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 60;
MATCH (p:Pengguna {nama: "Kartika"}), (k:Kursus {judul: "Pemrograman Python"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 85;
MATCH (p:Pengguna {nama: "Kartika"}), (k:Kursus {judul: "Jaringan Komputer"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 50;
MATCH (p:Pengguna {nama: "Laila"}), (k:Kursus {judul: "Statistika Terapan"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 80;
MATCH (p:Pengguna {nama: "Laila"}), (k:Kursus {judul: "Pemrograman Python"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 50;
MATCH (p:Pengguna {nama: "Laila"}), (k:Kursus {judul: "Jaringan Komputer"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 35;
MATCH (p:Pengguna {nama: "Maya"}), (k:Kursus {judul: "Basis Data Graf"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 90;
MATCH (p:Pengguna {nama: "Maya"}), (k:Kursus {judul: "Statistika Terapan"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 65;
MATCH (p:Pengguna {nama: "Maya"}), (k:Kursus {judul: "Rekayasa Perangkat Lunak"})
MERGE (p)-[r:MENGIKUTI]->(k)
SET r.progres = 30;# tidak-dijalankan
# memuat roster ke Neo4j melalui cypher-shell, kata sandi diminta interaktif
cypher-shell -a bolt://localhost:7687 -u neo4j -f setup.cypherKueri rekomendasi dua lompatan#
Pola dua lompatan mencari pengguna serupa melalui kursus yang diikuti bersama, lalu menjaring kursus lain yang diikuti pengguna serupa tersebut. Lompatan pertama bergerak dari Hesti menuju kursusnya, lompatan kedua kembali ke pengguna lain yang mengikuti kursus yang sama, dan satu MATCH tambahan menjangkau kursus milik pengguna serupa itu. Kueri final berikut diuji langsung di cypher-shell dengan nama Hesti.
MATCH (p:Pengguna {nama: "Hesti"})-[:MENGIKUTI]->(k:Kursus)<-[:MENGIKUTI]-(lain:Pengguna)
MATCH (lain)-[:MENGIKUTI]->(rek:Kursus)
WHERE NOT (p)-[:MENGIKUTI]->(rek)
RETURN rek.judul AS judul, count(*) AS skor
ORDER BY skor DESC, judul
LIMIT 3;judul, skor
"Jaringan Komputer", 5
"Statistika Terapan", 5Klausa WHERE NOT (p)-[:MENGIKUTI]->(rek) menyaring kursus yang sudah diikuti Hesti, termasuk kursus k pada lompatan pertama, sehingga yang tersisa hanya kandidat baru. Fungsi count(*) menghitung banyaknya pasangan kursus bersama dan pengguna serupa yang mengarah ke setiap kandidat, dan banyaknya pasangan itulah skor konsumsi bersamanya. Jaringan Komputer memperoleh skor 5 karena lima pasangan mengarah ke kursus tersebut, yaitu Kartika melalui Pengantar Algoritma, Basis Data Graf, dan Pemrograman Python, ditambah Joko melalui Basis Data Graf serta Laila melalui Pemrograman Python. Statistika Terapan juga memperoleh skor 5 dari lima pasangan, yaitu Indra melalui Pengantar Algoritma dan Pemrograman Python, Maya melalui Basis Data Graf dan Rekayasa Perangkat Lunak, serta Laila melalui Pemrograman Python.
Kedua kandidat sama-sama memperoleh skor 5, sehingga urutannya diputus oleh kunci kedua pada ORDER BY skor DESC, judul, yaitu abjad judul. Keluaran hanya memuat dua baris meskipun LIMIT 3 menetapkan batas tiga, karena Hesti sudah mengikuti empat dari enam kursus sehingga kandidat yang tersisa memang hanya dua. Pengurutan dan pembatasan dikerjakan di dalam kueri, bukan di sisi aplikasi, sehingga urutan yang sama dihasilkan pada setiap eksekusi selama isi roster tidak berubah, dan sifat menetap ini menjadi syarat penting ketika kuerinya dipindahkan ke aplikasi pada bagian berikutnya.
Koneksi melalui driver Python#
Driver Python resmi Neo4j dipasang melalui perintah pip install neo4j, dan kelas GraphDatabase.driver pada driver itu membuka koneksi Bolt ke server. Koneksi dideklarasikan dengan alamat bolt://localhost:7687 beserta pasangan nama pengguna dan sandi, sedangkan sandi dibaca dari variabel lingkungan NEO4J_PASSWORD dengan nilai cadangan untuk lingkungan pengembangan lokal. Kueri yang sudah teruji dipindahkan ke dalam konstanta dengan dua perubahan, yaitu nama Hesti menjadi parameter $nama dan batas jumlah menjadi parameter $batas. Pengiriman nilai melalui parameter memisahkan kueri dari datanya, sehingga teks kueri tidak pernah dirangkai dari masukan program.
Berkas rekomendasi.py berikut memuat fungsi ambil_rekomendasi(nama) yang mengembalikan list (daftar) pasangan judul dan skor, ditambah blok __main__ yang mencetak rekomendasi untuk Hesti. Sesi kueri dibuka dengan pernyataan with sehingga tertutup secara otomatis ketika bloknya berakhir, termasuk ketika error terjadi di tengah proses. Hasil kueri diubah menjadi list di dalam sesi tersebut, dan urutan pencetakannya mengikuti ORDER BY di dalam kueri tanpa pengurutan tambahan di sisi Python.
"""Mesin rekomendasi kursus di atas Neo4j.
Skrip ini hanya membaca data. Kuerinya mencari kursus yang diikuti pengguna
serupa melalui pola dua lompatan, menghitung skornya dengan count(), dan
mengembalikan tiga kursus teratas yang belum diikuti pengguna tersebut.
"""
import os
from neo4j import GraphDatabase
# ganti sesuai sandi basis data Anda
SANDI = os.environ.get("NEO4J_PASSWORD", "neopass2026")
URI = "bolt://localhost:7687"
PENGGUNA = "neo4j"
KUERI_REKOMENDASI = (
"MATCH (p:Pengguna {nama: $nama})-[:MENGIKUTI]->(k:Kursus)<-[:MENGIKUTI]-(lain:Pengguna) "
"MATCH (lain)-[:MENGIKUTI]->(rek:Kursus) "
"WHERE NOT (p)-[:MENGIKUTI]->(rek) "
"RETURN rek.judul AS judul, count(*) AS skor "
"ORDER BY skor DESC, judul "
"LIMIT $batas"
)
def ambil_rekomendasi(nama, batas=3):
"""Mengembalikan list pasangan (judul, skor) rekomendasi untuk satu pengguna."""
with GraphDatabase.driver(URI, auth=(PENGGUNA, SANDI)) as driver:
with driver.session() as sesi:
hasil = sesi.run(KUERI_REKOMENDASI, nama=nama, batas=batas)
return [(baris["judul"], baris["skor"]) for baris in hasil]
if __name__ == "__main__":
rekomendasi = ambil_rekomendasi("Hesti")
print("Rekomendasi untuk Hesti:")
for judul, skor in rekomendasi:
print(f"- {judul} (skor {skor})")Skripnya dijalankan dari folder topik dengan perintah berikut.
# menjalankan skrip rekomendasi dari folder topik
python3 rekomendasi.pyRekomendasi untuk Hesti:
- Jaringan Komputer (skor 5)
- Statistika Terapan (skor 5)Keluarannya sama dengan hasil kueri di cypher-shell, hanya format cetaknya yang disusun sendiri oleh program. Tidak ada data yang berubah karena kuerinya hanya memuat MATCH dan RETURN, sehingga skrip aman dijalankan berulang. Nilai NEO4J_PASSWORD yang diatur pada lingkungan akan digunakan sebagai sandi, sedangkan tanpa variabel itu sandi cadangan yang digunakan.
Kesalahan umum#
Kesalahan pertama adalah membuang klausa WHERE pada kueri rekomendasi. Tanpa penyaringan itu, kursus yang sudah diikuti pengguna justru muncul sebagai kandidat dengan skor tertinggi, karena kursus bersama pasti diikuti oleh pengguna serupa yang menjadikannya jembatan pola. Perbaikannya adalah mempertahankan WHERE NOT (p)-[:MENGIKUTI]->(rek) sehingga setiap kandidat benar-benar baru bagi pengguna yang dilayani.
Kesalahan kedua adalah membaca hasil kueri setelah sesinya tertutup. Objek hasil dari sesi.run menarik data dari server secara bertahap, sehingga pembacaannya harus selesai di dalam blok with. Apabila objek mentahnya dikembalikan keluar dari fungsi dan baru dibaca sesudahnya, driver menampilkan error ResultConsumedError. Perbaikannya adalah membentuk list di dalam sesi, sebagaimana dilakukan fungsi ambil_rekomendasi.
Kesalahan ketiga adalah sandi yang tidak cocok dengan server. Driver baru membuka koneksi ketika sesi pertama dibuat, sehingga error AuthError dengan kode Neo.ClientError.Security.Unauthorized muncul pada saat kueri dijalankan, bukan pada saat objek driver dibangun. Perbaikannya adalah menyamakan isi variabel lingkungan NEO4J_PASSWORD dengan sandi server, atau mengatur sandi servernya terlebih dahulu.
Ringkasan#
- Kueri rekomendasi menelusuri pola dua lompatan
:MENGIKUTIuntuk menemukan kursus yang diikuti pengguna serupa, menyaring kursus yang sudah diikuti denganWHERE, dan menghitung skornya dengancount(*)atas pasangan kursus bersama dan pengguna serupa. ORDER BY skor DESC, judulmenetapkan urutan hasil di dalam kueri, sedangkanLIMITmembatasi jumlahnya, sehingga keluaran tetap sama pada setiap eksekusi.- Driver Python resmi membuka koneksi
bolt://localhost:7687, sesinya ditangani pernyataanwith, dan nilai yang berubah dikirim sebagai parameter$namadan$batas. - Skrip
rekomendasi.pyhanya membaca data dan mencetak rekomendasi mengikuti urutan yang ditetapkan kuerinya.
Referensi#
- Build applications with Neo4j and Python — Neo4j Docs — Panduan resmi driver Python Neo4j, termasuk pembukaan koneksi, penanganan sesi, dan eksekusi kueri.
- Parameters — Neo4j Docs — Cara menuliskan parameter pada kueri Cypher dan mengirim nilainya dari driver.
- ORDER BY — Neo4j Docs — Referensi klausa pengurutan hasil kueri beserta arah naik dan turunnya.