Difference between revisions of "Data-cleaning-fisip"

From belajarwiki
Jump to navigation Jump to search
 
(4 intermediate revisions by the same user not shown)
Line 15: Line 15:
 
Step by step nya adalah
 
Step by step nya adalah
  
 +
==Data Cleaning==
 
===Import library dan membaca data===
 
===Import library dan membaca data===
 
<pre>
 
<pre>
Line 31: Line 32:
  
 
Klo di contoh saya nanti hasilnya adalah (97, 9). Artinya: 97 baris × 9 variabel.
 
Klo di contoh saya nanti hasilnya adalah (97, 9). Artinya: 97 baris × 9 variabel.
 +
 +
Cek nama variabel
 +
df.columns
 +
 +
 +
===Cek kondisi data===
 +
df.info()
 +
df.head()
 +
df.sample(5)
 +
df.isna().sum()
 +
 +
Untuk kasus saya, ada 15 posting tidak mempunyai display_name, namun kita cek pakah masih memiliki username
 +
<pre>
 +
df["display_name"] = (
 +
    df["display_name"]
 +
    .fillna(df["username"])
 +
)
 +
</pre>
 +
 +
Kemudian cek lagi
 +
df["display_name"].isna().sum()
 +
 +
Harus menghasilkan 0
 +
 +
Berarti dalam kasus ini display_name tidak terlalu penting karena masih ada username.
 +
 +
===Cek apakah ada duplicate===
 +
df.duplicated().sum()
 +
 +
Klo pada kasus saya, id unik nya adalah uri,jadi
 +
<pre>
 +
df.duplicated(
 +
    subset="uri"
 +
).sum()
 +
</pre>
 +
 +
Kode cleaning nya adalah
 +
<pre>
 +
df = df.drop_duplicates(
 +
    subset="uri"
 +
)
 +
</pre>
 +
 +
Kalau satu posting masuk dua kali, analisis kita dapat menghitung satu opini atau satu engagement dua kali.
 +
 +
===Rapihkan display_name===
 +
Beberapa nama mungkin memiliki spasi tambahan.
 +
<pre>
 +
df["display_name"] = (
 +
    df["display_name"]
 +
    .astype(str)
 +
    .str.strip()
 +
)
 +
</pre>
 +
 +
Kita rapihkan juga username
 +
<pre>
 +
df["username"] = (
 +
    df["username"]
 +
    .astype(str)
 +
    .str.strip()
 +
    .str.lower()
 +
)
 +
</pre>
 +
 +
===Perbaiki type tanggal===
 +
Kita cek tipe datanya
 +
df["created_at"].dtype
 +
 +
Jika hasilnya = object, berarti masih dianggap teks, jadi harus diubah ke tipe waktu.
 +
<pre>
 +
df["created_at"] = pd.to_datetime(
 +
    df["created_at"],
 +
    format="mixed",
 +
    utc=True,
 +
    errors="coerce"
 +
)
 +
</pre>
 +
 +
===Ubah UTC ke waktu Jakarta===
 +
<pre>
 +
df["created_at_wib"] = (
 +
    df["created_at"]
 +
    .dt.tz_convert("Asia/Jakarta")
 +
)
 +
</pre>
 +
 +
Kemudian cek
 +
<pre>
 +
df[
 +
    ["created_at", "created_at_wib"]
 +
].head()
 +
</pre>
 +
 +
===Rapihkan kolom bahasa===
 +
df["language"].value_counts()
 +
 +
Perbaiki
 +
<pre>
 +
df["language_primary"] = (
 +
    df["language"]
 +
    .astype(str)
 +
    .str.extract(
 +
        r"([A-Za-z]+(?:-[A-Za-z]+)?)",
 +
        expand=False
 +
    )
 +
    .fillna("unknown")
 +
)
 +
</pre>
 +
 +
Cek
 +
df["language_primary"].value_counts()
 +
 +
===Pastikan reply, repost dan like benar-benar numerik===
 +
Cek
 +
<pre>
 +
df[
 +
    [
 +
        "reply_count",
 +
        "repost_count",
 +
        "like_count"
 +
    ]
 +
].dtypes
 +
</pre>
 +
 +
Jika masih belum angka
 +
<pre>
 +
kolom_angka = [
 +
    "reply_count",
 +
    "repost_count",
 +
    "like_count"
 +
]
 +
 +
for kolom in kolom_angka:
 +
    df[kolom] = pd.to_numeric(
 +
        df[kolom],
 +
        errors="coerce"
 +
    ).fillna(0).astype(int)
 +
</pre>
 +
 +
===Jika ada URL terambil===
 +
Buat variabel, barangkali nanti url tersebut dibutuhkan
 +
<pre>
 +
df["has_url"] = (
 +
    df["text"]
 +
    .str.contains(
 +
        r"https?://|www\.",
 +
        regex=True,
 +
        na=False
 +
    )
 +
)
 +
</pre>
 +
 +
Cek
 +
df["has_url"].value_counts()
 +
 +
===Deteksi Hastag===
 +
<pre>
 +
df["has_hashtag"] = (
 +
    df["text"]
 +
    .str.contains(
 +
        r"#\w+",
 +
        regex=True,
 +
        na=False
 +
    )
 +
)
 +
</pre>
 +
 +
Cek
 +
df["has_hashtag"].value_counts()
 +
 +
==Preprocessing==
 +
===Buat Clean Text===
 +
Kita mulai masuk ke preprocessing
 +
<pre>
 +
df["text_clean"] = (
 +
    df["text"]
 +
    .fillna("")
 +
    .astype(str)
 +
)
 +
</pre>
 +
 +
Hapus URL
 +
<pre>
 +
df["text_clean"] = (
 +
    df["text_clean"]
 +
    .str.replace(
 +
        r"https?://\S+|www\.\S+",
 +
        " ",
 +
        regex=True
 +
    )
 +
)
 +
</pre>
 +
 +
Nanti hasilnya misal: <b>Climate change is real https://example.com</b> -> <b>Climate change is real</b>
 +
 +
===Hilangkan mention===
 +
<pre>
 +
df["text_clean"] = (
 +
    df["text_clean"]
 +
    .str.replace(
 +
        r"@[\w.-]+",
 +
        " ",
 +
        regex=True
 +
    )
 +
)
 +
</pre>
 +
 +
Namun mention tidak selalu harus dibuang. Kalau kita ingin melakukan network analysis, mention justru merupakan data yang penting.
 +
 +
===Hastag===
 +
Jangan buang katanya, namun tanda "#" saja
 +
<pre>
 +
df["text_clean"] = (
 +
    df["text_clean"]
 +
    .str.replace(
 +
        r"#(\w+)",
 +
        r"\1",
 +
        regex=True
 +
    )
 +
)
 +
</pre>
 +
 +
===Ubah menjadi lowercase===
 +
<pre>
 +
df["text_clean"] = (
 +
    df["text_clean"]
 +
    .str.lower()
 +
)
 +
</pre>
 +
Ini penting agar komputer tidak menganggap kata yang sama namun misal ada huruf kapital, sebagai kata berbeda.
 +
 +
===Rapikan line break dan spasi===
 +
<pre>
 +
df["text_clean"] = (
 +
    df["text_clean"]
 +
    .str.replace(
 +
        r"\s+",
 +
        " ",
 +
        regex=True
 +
    )
 +
    .str.strip()
 +
)
 +
</pre>
 +
 +
===Bandingkan sebelum dan sesudah===
 +
<pre>
 +
df[
 +
    [
 +
        "text",
 +
        "text_clean"
 +
    ]
 +
].head(10)
 +
</pre>
 +
 +
===Buat variabel engagement===
 +
Misal kita perlu untuk menghitung hubungannya.
 +
Kita definisikan secara sederhana:Engagement = Reply + Repost + Like
 +
 +
<pre>
 +
df["engagement"] = (
 +
    df["reply_count"]
 +
    + df["repost_count"]
 +
    + df["like_count"]
 +
)
 +
</pre>
 +
 +
Kita cek
 +
<pre>
 +
df[
 +
    [
 +
        "reply_count",
 +
        "repost_count",
 +
        "like_count",
 +
        "engagement"
 +
    ]
 +
].head()
 +
</pre>
 +
 +
Ini adalah definisi operasional untuk latihan, bukan definisi universal engagement. Kalau penelitian sebenarnya, Anda harus menjelaskan mengapa reply, repost, dan like dijumlahkan dan apakah bobotnya memang seharusnya sama.
 +
 +
===Buat jumlah kata===
 +
<pre>
 +
df["word_count"] = (
 +
    df["text_clean"]
 +
    .str.findall(
 +
        r"\b[\w’'-]+\b"
 +
    )
 +
    .str.len()
 +
)
 +
</pre>
 +
 +
Cek
 +
df["word_count"].describe()
 +
 +
===Buat variabel tanggal dan jam===
 +
<pre>
 +
df["date"] = (
 +
    df["created_at_wib"]
 +
    .dt.date
 +
)
 +
 +
df["hour"] = (
 +
    df["created_at_wib"]
 +
    .dt.hour
 +
)
 +
</pre>
 +
 +
Cleaning = memperbaiki data.
 +
Preprocessing = membuat data menjadi lebih siap untuk analisis.
 +
 +
===Validasi===
 +
Jumlah data:
 +
len(df)
 +
 +
Missing value:
 +
df.isna().sum()
 +
 +
Duplicate:
 +
df.duplicated(subset="uri").sum()
 +
 +
Statistik engagement:
 +
df["engagement"].describe()
 +
 +
Tampilkan hasil:
 +
df.head()
 +
 +
Kode berhasil dijalankan belum berarti data sudah benar. Kita tetap harus memeriksa hasil.
 +
 +
===Simpan===
 +
<pre>
 +
df.to_csv(
 +
    "bluesky_climate_change_clean.csv",
 +
    index=False,
 +
    encoding="utf-8"
 +
)
 +
</pre>
 +
 +
==Kode lengkap==
 +
<pre>
 +
import pandas as pd
 +
import re
 +
 +
# ==========================================
 +
# 1. LOAD DATA
 +
# ==========================================
 +
 +
df = pd.read_csv(
 +
    "bluesky_climate_change_raw.csv"
 +
)
 +
 +
 +
# ==========================================
 +
# 2. DUPLICATE
 +
# ==========================================
 +
 +
df = df.drop_duplicates(
 +
    subset="uri"
 +
)
 +
 +
 +
# ==========================================
 +
# 3. MISSING DISPLAY NAME
 +
# ==========================================
 +
 +
df["display_name"] = (
 +
    df["display_name"]
 +
    .fillna(df["username"])
 +
    .astype(str)
 +
    .str.strip()
 +
)
 +
 +
df["username"] = (
 +
    df["username"]
 +
    .astype(str)
 +
    .str.strip()
 +
    .str.lower()
 +
)
 +
 +
 +
# ==========================================
 +
# 4. DATE / TIME
 +
# ==========================================
 +
 +
df["created_at"] = pd.to_datetime(
 +
    df["created_at"],
 +
    format="mixed",
 +
    utc=True,
 +
    errors="coerce"
 +
)
 +
 +
df["created_at_wib"] = (
 +
    df["created_at"]
 +
    .dt.tz_convert("Asia/Jakarta")
 +
)
 +
 +
 +
# ==========================================
 +
# 5. LANGUAGE
 +
# ==========================================
 +
 +
df["language_primary"] = (
 +
    df["language"]
 +
    .astype(str)
 +
    .str.extract(
 +
        r"([A-Za-z]+(?:-[A-Za-z]+)?)",
 +
        expand=False
 +
    )
 +
    .fillna("unknown")
 +
)
 +
 +
 +
# ==========================================
 +
# 6. NUMERIC VARIABLES
 +
# ==========================================
 +
 +
kolom_angka = [
 +
    "reply_count",
 +
    "repost_count",
 +
    "like_count"
 +
]
 +
 +
for kolom in kolom_angka:
 +
 +
    df[kolom] = pd.to_numeric(
 +
        df[kolom],
 +
        errors="coerce"
 +
    ).fillna(0).astype(int)
 +
 +
 +
# ==========================================
 +
# 7. INFORMATION FROM ORIGINAL TEXT
 +
# ==========================================
 +
 +
df["has_url"] = (
 +
    df["text"]
 +
    .str.contains(
 +
        r"https?://|www\.",
 +
        regex=True,
 +
        na=False
 +
    )
 +
)
 +
 +
df["has_hashtag"] = (
 +
    df["text"]
 +
    .str.contains(
 +
        r"#\w+",
 +
        regex=True,
 +
        na=False
 +
    )
 +
)
 +
 +
 +
# ==========================================
 +
# 8. TEXT CLEANING
 +
# ==========================================
 +
 +
df["text_clean"] = (
 +
    df["text"]
 +
    .fillna("")
 +
    .astype(str)
 +
)
 +
 +
# Remove URL
 +
df["text_clean"] = (
 +
    df["text_clean"]
 +
    .str.replace(
 +
        r"https?://\S+|www\.\S+",
 +
        " ",
 +
        regex=True
 +
    )
 +
)
 +
 +
# Remove mentions
 +
df["text_clean"] = (
 +
    df["text_clean"]
 +
    .str.replace(
 +
        r"@[\w.-]+",
 +
        " ",
 +
        regex=True
 +
    )
 +
)
 +
 +
# Keep hashtag word, remove #
 +
df["text_clean"] = (
 +
    df["text_clean"]
 +
    .str.replace(
 +
        r"#(\w+)",
 +
        r"\1",
 +
        regex=True
 +
    )
 +
)
 +
 +
# Lowercase
 +
df["text_clean"] = (
 +
    df["text_clean"]
 +
    .str.lower()
 +
)
 +
 +
# Remove extra whitespace
 +
df["text_clean"] = (
 +
    df["text_clean"]
 +
    .str.replace(
 +
        r"\s+",
 +
        " ",
 +
        regex=True
 +
    )
 +
    .str.strip()
 +
)
 +
 +
 +
# ==========================================
 +
# 9. DERIVED VARIABLES
 +
# ==========================================
 +
 +
df["engagement"] = (
 +
    df["reply_count"]
 +
    + df["repost_count"]
 +
    + df["like_count"]
 +
)
 +
 +
df["word_count"] = (
 +
    df["text_clean"]
 +
    .str.findall(
 +
        r"\b[\w’'-]+\b"
 +
    )
 +
    .str.len()
 +
)
 +
 +
df["date"] = (
 +
    df["created_at_wib"]
 +
    .dt.date
 +
)
 +
 +
df["hour"] = (
 +
    df["created_at_wib"]
 +
    .dt.hour
 +
)
 +
 +
 +
# ==========================================
 +
# 10. VALIDATION
 +
# ==========================================
 +
 +
print("Jumlah data:", len(df))
 +
 +
print(
 +
    "\nMissing value:"
 +
)
 +
 +
print(
 +
    df.isna().sum()
 +
)
 +
 +
print(
 +
    "\nDuplicate URI:",
 +
    df.duplicated(
 +
        subset="uri"
 +
    ).sum()
 +
)
 +
 +
print(
 +
    "\nStatistik engagement:"
 +
)
 +
 +
print(
 +
    df["engagement"].describe()
 +
)
 +
 +
 +
# ==========================================
 +
# 11. SAVE
 +
# ==========================================
 +
 +
df.to_csv(
 +
    "bluesky_climate_change_clean.csv",
 +
    index=False,
 +
    encoding="utf-8"
 +
)
 +
 +
print(
 +
    "\nData bersih berhasil disimpan."
 +
)
 +
</pre>
 +
 +
==Analisis yang bisa dilakukan==
 +
===Seberapa tinggi engagement?===
 +
df["engagement"].describe()
 +
 +
===Posting dengan engagement tertinggi===
 +
<pre>
 +
top10 = (
 +
    df.sort_values(
 +
        "engagement",
 +
        ascending=False
 +
    )
 +
    [
 +
        [
 +
            "username",
 +
            "text_clean",
 +
            "reply_count",
 +
            "repost_count",
 +
            "like_count",
 +
            "engagement"
 +
        ]
 +
    ]
 +
    .head(10)
 +
)
 +
 +
top10
 +
</pre>
 +
 +
===Visualisasi Top 10 Engagement===
 +
<pre>
 +
import matplotlib.pyplot as plt
 +
 +
top10_plot = (
 +
    df.nlargest(
 +
        10,
 +
        "engagement"
 +
    )
 +
    .sort_values(
 +
        "engagement"
 +
    )
 +
)
 +
 +
top10_plot.plot(
 +
    x="username",
 +
    y="engagement",
 +
    kind="barh",
 +
    legend=False
 +
)
 +
 +
plt.title(
 +
    "10 Posting dengan Engagement Tertinggi"
 +
)
 +
 +
plt.xlabel(
 +
    "Engagement"
 +
)
 +
 +
plt.ylabel(
 +
    "Akun"
 +
)
 +
 +
plt.show()
 +
</pre>
 +
 +
===Akun paling aktif===
 +
<pre>
 +
df[
 +
    "username"
 +
].value_counts().head(10)
 +
</pre>
 +
 +
===Aktivitas berdasarkan jam===
 +
<pre>
 +
posting_per_jam = (
 +
    df["hour"]
 +
    .value_counts()
 +
    .sort_index()
 +
)
 +
 +
posting_per_jam
 +
</pre>
 +
 +
Visualisasi
 +
<pre>
 +
posting_per_jam.plot(
 +
    kind="bar"
 +
)
 +
 +
plt.title(
 +
    "Jumlah Posting per Jam"
 +
)
 +
 +
plt.xlabel(
 +
    "Jam (WIB)"
 +
)
 +
 +
plt.ylabel(
 +
    "Jumlah Posting"
 +
)
 +
 +
plt.show()
 +
</pre>
 +
 +
===Kata yang sering muncul===
 +
<pre>
 +
from collections import Counter
 +
 +
semua_teks = " ".join(
 +
    df["text_clean"]
 +
)
 +
 +
kata = re.findall(
 +
    r"[a-zA-Z][a-zA-Z'-]+",
 +
    semua_teks
 +
)
 +
 +
stopwords = {
 +
    "the", "and", "to", "of",
 +
    "a", "in", "is", "for",
 +
    "that", "it", "on", "with",
 +
    "are", "be", "as", "this",
 +
    "we", "i", "you", "they",
 +
    "have", "has", "was",
 +
    "were", "not", "but",
 +
 +
    # kata pencarian
 +
    "climate",
 +
    "change",
 +
    "climatechange"
 +
}
 +
 +
kata_bersih = [
 +
    k
 +
    for k in kata
 +
    if k not in stopwords
 +
    and len(k) > 2
 +
]
 +
 +
frekuensi = Counter(
 +
    kata_bersih
 +
)
 +
 +
frekuensi.most_common(20)
 +
</pre>
 +
 +
===Visualisasi 15 kata teratas===
 +
<pre>
 +
freq_df = pd.DataFrame(
 +
    frekuensi.most_common(15),
 +
    columns=[
 +
        "word",
 +
        "frequency"
 +
    ]
 +
)
 +
 +
freq_df = freq_df.sort_values(
 +
    "frequency"
 +
)
 +
 +
freq_df.plot(
 +
    x="word",
 +
    y="frequency",
 +
    kind="barh",
 +
    legend=False
 +
)
 +
 +
plt.title(
 +
    "15 Kata yang Sering Muncul"
 +
)
 +
 +
plt.xlabel(
 +
    "Frekuensi"
 +
)
 +
 +
plt.ylabel(
 +
    "Kata"
 +
)
 +
 +
plt.show()
 +
</pre>
 +
 +
 +
==Link Terkait==
 +
* [[Big Data Fisip]]

Latest revision as of 14:05, 30 September 2026

Dari contoh yang didapat, kita akan melakukan hal berikut

bluesky_climate_change_raw.csv
            ↓
      DATA CLEANING
            ↓
      PREPROCESSING
            ↓
bluesky_climate_change_clean.csv
            ↓
        ANALISIS

Step by step nya adalah

Data Cleaning

Import library dan membaca data

import pandas as pd
import re

df = pd.read_csv(
    "bluesky_climate_change_raw.csv"
)

df.head()

Cek ukuran data

df.shape

Klo di contoh saya nanti hasilnya adalah (97, 9). Artinya: 97 baris × 9 variabel.

Cek nama variabel

df.columns


Cek kondisi data

df.info()
df.head()
df.sample(5)
df.isna().sum()

Untuk kasus saya, ada 15 posting tidak mempunyai display_name, namun kita cek pakah masih memiliki username

df["display_name"] = (
    df["display_name"]
    .fillna(df["username"])
)

Kemudian cek lagi

df["display_name"].isna().sum()

Harus menghasilkan 0

Berarti dalam kasus ini display_name tidak terlalu penting karena masih ada username.

Cek apakah ada duplicate

df.duplicated().sum()

Klo pada kasus saya, id unik nya adalah uri,jadi

df.duplicated(
    subset="uri"
).sum()

Kode cleaning nya adalah

df = df.drop_duplicates(
    subset="uri"
)

Kalau satu posting masuk dua kali, analisis kita dapat menghitung satu opini atau satu engagement dua kali.

Rapihkan display_name

Beberapa nama mungkin memiliki spasi tambahan.

df["display_name"] = (
    df["display_name"]
    .astype(str)
    .str.strip()
)

Kita rapihkan juga username

df["username"] = (
    df["username"]
    .astype(str)
    .str.strip()
    .str.lower()
)

Perbaiki type tanggal

Kita cek tipe datanya

df["created_at"].dtype

Jika hasilnya = object, berarti masih dianggap teks, jadi harus diubah ke tipe waktu.

df["created_at"] = pd.to_datetime(
    df["created_at"],
    format="mixed",
    utc=True,
    errors="coerce"
)

Ubah UTC ke waktu Jakarta

df["created_at_wib"] = (
    df["created_at"]
    .dt.tz_convert("Asia/Jakarta")
)

Kemudian cek

df[
    ["created_at", "created_at_wib"]
].head()

Rapihkan kolom bahasa

df["language"].value_counts()

Perbaiki

df["language_primary"] = (
    df["language"]
    .astype(str)
    .str.extract(
        r"([A-Za-z]+(?:-[A-Za-z]+)?)",
        expand=False
    )
    .fillna("unknown")
)

Cek

df["language_primary"].value_counts()

Pastikan reply, repost dan like benar-benar numerik

Cek

df[
    [
        "reply_count",
        "repost_count",
        "like_count"
    ]
].dtypes

Jika masih belum angka

kolom_angka = [
    "reply_count",
    "repost_count",
    "like_count"
]

for kolom in kolom_angka:
    df[kolom] = pd.to_numeric(
        df[kolom],
        errors="coerce"
    ).fillna(0).astype(int)

Jika ada URL terambil

Buat variabel, barangkali nanti url tersebut dibutuhkan

df["has_url"] = (
    df["text"]
    .str.contains(
        r"https?://|www\.",
        regex=True,
        na=False
    )
)

Cek

df["has_url"].value_counts()

Deteksi Hastag

df["has_hashtag"] = (
    df["text"]
    .str.contains(
        r"#\w+",
        regex=True,
        na=False
    )
)

Cek

df["has_hashtag"].value_counts()

Preprocessing

Buat Clean Text

Kita mulai masuk ke preprocessing

df["text_clean"] = (
    df["text"]
    .fillna("")
    .astype(str)
)

Hapus URL

df["text_clean"] = (
    df["text_clean"]
    .str.replace(
        r"https?://\S+|www\.\S+",
        " ",
        regex=True
    )
)

Nanti hasilnya misal: Climate change is real https://example.com -> Climate change is real

Hilangkan mention

df["text_clean"] = (
    df["text_clean"]
    .str.replace(
        r"@[\w.-]+",
        " ",
        regex=True
    )
)

Namun mention tidak selalu harus dibuang. Kalau kita ingin melakukan network analysis, mention justru merupakan data yang penting.

Hastag

Jangan buang katanya, namun tanda "#" saja

df["text_clean"] = (
    df["text_clean"]
    .str.replace(
        r"#(\w+)",
        r"\1",
        regex=True
    )
)

Ubah menjadi lowercase

df["text_clean"] = (
    df["text_clean"]
    .str.lower()
)

Ini penting agar komputer tidak menganggap kata yang sama namun misal ada huruf kapital, sebagai kata berbeda.

Rapikan line break dan spasi

df["text_clean"] = (
    df["text_clean"]
    .str.replace(
        r"\s+",
        " ",
        regex=True
    )
    .str.strip()
)

Bandingkan sebelum dan sesudah

df[
    [
        "text",
        "text_clean"
    ]
].head(10)

Buat variabel engagement

Misal kita perlu untuk menghitung hubungannya. Kita definisikan secara sederhana:Engagement = Reply + Repost + Like

df["engagement"] = (
    df["reply_count"]
    + df["repost_count"]
    + df["like_count"]
)

Kita cek

df[
    [
        "reply_count",
        "repost_count",
        "like_count",
        "engagement"
    ]
].head()

Ini adalah definisi operasional untuk latihan, bukan definisi universal engagement. Kalau penelitian sebenarnya, Anda harus menjelaskan mengapa reply, repost, dan like dijumlahkan dan apakah bobotnya memang seharusnya sama.

Buat jumlah kata

df["word_count"] = (
    df["text_clean"]
    .str.findall(
        r"\b[\w’'-]+\b"
    )
    .str.len()
)

Cek

df["word_count"].describe()

Buat variabel tanggal dan jam

df["date"] = (
    df["created_at_wib"]
    .dt.date
)

df["hour"] = (
    df["created_at_wib"]
    .dt.hour
)

Cleaning = memperbaiki data. Preprocessing = membuat data menjadi lebih siap untuk analisis.

Validasi

Jumlah data:

len(df)

Missing value:

df.isna().sum()

Duplicate:

df.duplicated(subset="uri").sum()

Statistik engagement:

df["engagement"].describe()

Tampilkan hasil:

df.head()

Kode berhasil dijalankan belum berarti data sudah benar. Kita tetap harus memeriksa hasil.

Simpan

df.to_csv(
    "bluesky_climate_change_clean.csv",
    index=False,
    encoding="utf-8"
)

Kode lengkap

import pandas as pd
import re

# ==========================================
# 1. LOAD DATA
# ==========================================

df = pd.read_csv(
    "bluesky_climate_change_raw.csv"
)


# ==========================================
# 2. DUPLICATE
# ==========================================

df = df.drop_duplicates(
    subset="uri"
)


# ==========================================
# 3. MISSING DISPLAY NAME
# ==========================================

df["display_name"] = (
    df["display_name"]
    .fillna(df["username"])
    .astype(str)
    .str.strip()
)

df["username"] = (
    df["username"]
    .astype(str)
    .str.strip()
    .str.lower()
)


# ==========================================
# 4. DATE / TIME
# ==========================================

df["created_at"] = pd.to_datetime(
    df["created_at"],
    format="mixed",
    utc=True,
    errors="coerce"
)

df["created_at_wib"] = (
    df["created_at"]
    .dt.tz_convert("Asia/Jakarta")
)


# ==========================================
# 5. LANGUAGE
# ==========================================

df["language_primary"] = (
    df["language"]
    .astype(str)
    .str.extract(
        r"([A-Za-z]+(?:-[A-Za-z]+)?)",
        expand=False
    )
    .fillna("unknown")
)


# ==========================================
# 6. NUMERIC VARIABLES
# ==========================================

kolom_angka = [
    "reply_count",
    "repost_count",
    "like_count"
]

for kolom in kolom_angka:

    df[kolom] = pd.to_numeric(
        df[kolom],
        errors="coerce"
    ).fillna(0).astype(int)


# ==========================================
# 7. INFORMATION FROM ORIGINAL TEXT
# ==========================================

df["has_url"] = (
    df["text"]
    .str.contains(
        r"https?://|www\.",
        regex=True,
        na=False
    )
)

df["has_hashtag"] = (
    df["text"]
    .str.contains(
        r"#\w+",
        regex=True,
        na=False
    )
)


# ==========================================
# 8. TEXT CLEANING
# ==========================================

df["text_clean"] = (
    df["text"]
    .fillna("")
    .astype(str)
)

# Remove URL
df["text_clean"] = (
    df["text_clean"]
    .str.replace(
        r"https?://\S+|www\.\S+",
        " ",
        regex=True
    )
)

# Remove mentions
df["text_clean"] = (
    df["text_clean"]
    .str.replace(
        r"@[\w.-]+",
        " ",
        regex=True
    )
)

# Keep hashtag word, remove #
df["text_clean"] = (
    df["text_clean"]
    .str.replace(
        r"#(\w+)",
        r"\1",
        regex=True
    )
)

# Lowercase
df["text_clean"] = (
    df["text_clean"]
    .str.lower()
)

# Remove extra whitespace
df["text_clean"] = (
    df["text_clean"]
    .str.replace(
        r"\s+",
        " ",
        regex=True
    )
    .str.strip()
)


# ==========================================
# 9. DERIVED VARIABLES
# ==========================================

df["engagement"] = (
    df["reply_count"]
    + df["repost_count"]
    + df["like_count"]
)

df["word_count"] = (
    df["text_clean"]
    .str.findall(
        r"\b[\w’'-]+\b"
    )
    .str.len()
)

df["date"] = (
    df["created_at_wib"]
    .dt.date
)

df["hour"] = (
    df["created_at_wib"]
    .dt.hour
)


# ==========================================
# 10. VALIDATION
# ==========================================

print("Jumlah data:", len(df))

print(
    "\nMissing value:"
)

print(
    df.isna().sum()
)

print(
    "\nDuplicate URI:",
    df.duplicated(
        subset="uri"
    ).sum()
)

print(
    "\nStatistik engagement:"
)

print(
    df["engagement"].describe()
)


# ==========================================
# 11. SAVE
# ==========================================

df.to_csv(
    "bluesky_climate_change_clean.csv",
    index=False,
    encoding="utf-8"
)

print(
    "\nData bersih berhasil disimpan."
)

Analisis yang bisa dilakukan

Seberapa tinggi engagement?

df["engagement"].describe()

Posting dengan engagement tertinggi

top10 = (
    df.sort_values(
        "engagement",
        ascending=False
    )
    [
        [
            "username",
            "text_clean",
            "reply_count",
            "repost_count",
            "like_count",
            "engagement"
        ]
    ]
    .head(10)
)

top10

Visualisasi Top 10 Engagement

import matplotlib.pyplot as plt

top10_plot = (
    df.nlargest(
        10,
        "engagement"
    )
    .sort_values(
        "engagement"
    )
)

top10_plot.plot(
    x="username",
    y="engagement",
    kind="barh",
    legend=False
)

plt.title(
    "10 Posting dengan Engagement Tertinggi"
)

plt.xlabel(
    "Engagement"
)

plt.ylabel(
    "Akun"
)

plt.show()

Akun paling aktif

df[
    "username"
].value_counts().head(10)

Aktivitas berdasarkan jam

posting_per_jam = (
    df["hour"]
    .value_counts()
    .sort_index()
)

posting_per_jam

Visualisasi

posting_per_jam.plot(
    kind="bar"
)

plt.title(
    "Jumlah Posting per Jam"
)

plt.xlabel(
    "Jam (WIB)"
)

plt.ylabel(
    "Jumlah Posting"
)

plt.show()

Kata yang sering muncul

from collections import Counter

semua_teks = " ".join(
    df["text_clean"]
)

kata = re.findall(
    r"[a-zA-Z][a-zA-Z'-]+",
    semua_teks
)

stopwords = {
    "the", "and", "to", "of",
    "a", "in", "is", "for",
    "that", "it", "on", "with",
    "are", "be", "as", "this",
    "we", "i", "you", "they",
    "have", "has", "was",
    "were", "not", "but",

    # kata pencarian
    "climate",
    "change",
    "climatechange"
}

kata_bersih = [
    k
    for k in kata
    if k not in stopwords
    and len(k) > 2
]

frekuensi = Counter(
    kata_bersih
)

frekuensi.most_common(20)

Visualisasi 15 kata teratas

freq_df = pd.DataFrame(
    frekuensi.most_common(15),
    columns=[
        "word",
        "frequency"
    ]
)

freq_df = freq_df.sort_values(
    "frequency"
)

freq_df.plot(
    x="word",
    y="frequency",
    kind="barh",
    legend=False
)

plt.title(
    "15 Kata yang Sering Muncul"
)

plt.xlabel(
    "Frekuensi"
)

plt.ylabel(
    "Kata"
)

plt.show()


Link Terkait