← Retour à l'épreuve

Indonesia IOAI selection 2025 · Correction IOAI Training

Exploratory Data Analysis of Cinema Ticket Sales

Cette page donne toute la méthode. Lisez-la une fois que vous avez cherché.

Métrique
points (100 + 10 bonus)
Évaluée sur
written answers
Notre score
all 5 answers + bonus, verified
Niveau attendu
exact answers to Soal 1-5

Niveau attendu atteint

Nous avons résolu l'épreuve nous-mêmes : téléchargé les données, mené les expériences et mesuré le score ci-dessous. L'énoncé et les données appartiennent à leurs organisateurs. Cette correction est rédigée en anglais.

The problem in brief

The notebook embeds a small table of 60 cinema screenings in Indonesian cities in June 2025. Each row gives the film title, a genre, the ticket price in rupiah, the number of tickets sold, the date, the city, the show time and the studio. You must answer five aggregation questions: revenue per genre, mean price per genre, the busiest date, the city that showed the most distinct films and the highest-grossing film. A bonus asks for one interesting insight of your own. It is a pandas warm-up from the exhibition final of the Indonesian national science olympiad (OSN) in AI. The real test is whether you look at the data before grouping it: one of the columns is dirty and silently breaks the naive answer.

How you are scored

The five questions are worth 10, 15, 20, 30 and 25 points (100 in total), plus 10 bonus points. Each answer is a value checked against the organisers' key, and the notebook asks you not to modify the data. The heaviest question (30 points) is the one with the data-cleaning trap.

First look at the data

Before any groupby, list the distinct values of every text column. Three findings:

  1. The city column has 24 spellings for 7 cities: SBY, Surabaya, SURABAYA and surabaya are all Surabaya; SMG is Semarang, JKT Jakarta, JOGJA Yogyakarta; and every city appears in several capitalisations.
  2. The genre is not a property of the film. Frozen II appears with five genres (Animasi, Sci-Fi, Drama, Komedi, Horor), and 17 of the 23 titles have more than one. The genre seems to be attached to the screening at random.
  3. Your Name and Kimi no Na wa are the same film (English and Japanese titles). Here merging them changes none of the answers, but it is worth checking.

Also note that the description announces data from 1 to 20 June, while the dates only run from 1 to 15 June.

The answers

Revenue of a screening == harga_tiket ×\times jumlah_tiket; we add a column pendapatan once and reuse it.

Soal 1, total revenue per genre.

df["pendapatan"] = df["harga_tiket"] * df["jumlah_tiket"]
df.groupby("genre")["pendapatan"].sum()
genrerevenue (Rp)
Komedi113 780 000
Drama71 450 000
Action71 200 000
Sci-Fi58 275 000
Horor52 300 000
Animasi33 285 000

Soal 2, mean ticket price per genre (the average over screenings, df.groupby("genre")["harga_tiket"].mean()): Action 61 666.67, Animasi 65 000.00, Drama 65 000.00, Horor 75 833.33, Komedi 67 941.18, Sci-Fi 67 777.78. (This is the plain mean price. A ticket-weighted mean, total revenue divided by total tickets, would answer the different question "what did the average customer pay?".)

Soal 3, busiest date: 2025-06-01, with 737 tickets across all films (next come 2 June and 15 June with 680 each).

df.groupby("tanggal")["jumlah_tiket"].sum().idxmax()

Soal 4, city with the most distinct films: Semarang and Surabaya, tied with 9 titles each. Grouping the raw column gives SBY, SMG and MEDAN tied at 5: a wrong answer that looks plausible. Normalise first:

ALIAS = {"sby": "Surabaya", "surabaya": "Surabaya", "smg": "Semarang", "semarang": "Semarang",
         "jkt": "Jakarta", "jakarta": "Jakarta", "jogja": "Yogyakarta", "yogyakarta": "Yogyakarta",
         "makassar": "Makassar", "medan": "Medan", "bandung": "Bandung"}
df["kota_bersih"] = df["kota"].str.strip().str.lower().map(ALIAS)
assert df["kota_bersih"].notna().all()            # every spelling is covered
df.groupby("kota_bersih")["judul_film"].nunique()

Lower-casing alone is not enough, because the abbreviations still need an explicit mapping. The assert protects you against a spelling the mapping forgot. Because of the tie, a correct answer reports both cities rather than whichever idxmax happens to return first.

Left: unique film titles per raw spelling of the city, 24 bars with Medan, SBY and SMG on top at 5. Right: after normalisation, 7 cities with Surabaya and Semarang tied at 9, then Makassar 8, Yogyakarta and Jakarta 7, Medan 5, Bandung 4.

Soal 5, highest-grossing film: Frozen II, with Rp 41 045 000 over its 7 screenings, ahead of Captain America (39 090 000) and Conjuring: The Devil Made Me Do It (37 255 000).

df.groupby("judul_film")["pendapatan"].sum().idxmax()

Bonus insight

Our pick is the most consequential finding: the genre column is unreliable, which makes Soal 1 and 2 meaningless as statements about genres. The same film carries several genres (Frozen II has five, Captain America and Conjuring four each; 17 of 23 titles have more than one). "Komedi earns the most" therefore says nothing about comedies. It only says that the label Komedi was attached to the most screenings (17 of 60). In a real analysis you would rebuild the genre from a film-level reference table (one genre per title), then redo Soal 1 and 2.

Two other insights the data supports:

  • Price and attendance are essentially unrelated: the correlation between ticket price and tickets sold is r=−0.05r = -0.05. Higher prices do not visibly reduce attendance in this sample.
  • Studio D has the highest mean revenue per screening (about Rp 7.1 million, against 6.2 to 6.8 million for A, B and C), although it hosts the fewest screenings (12 against 16).

What to remember

  • Inspect value_counts() / unique() of every categorical column before grouping. Inconsistent spellings split one category into many and give confidently wrong answers.
  • Normalise with an explicit mapping, then assert that nothing is left unmapped; lower-casing does not fix abbreviations.
  • Report ties honestly instead of trusting whatever idxmax returns first.
  • Check that a label really describes the entity you group by: a genre that changes from row to row for the same film cannot support conclusions about genres.
  • Be precise about which mean you compute: the mean over rows and the weighted mean answer different questions.
Code complet (solution.py)
"""OSN AI 2025 exhibition final: EDA of cinema ticket sales (Indonesia). Answers to Soal 1-5 + bonus, and the figure.
The dataset is the literal list given in the notebook (60 screenings), copied verbatim below."""
import pandas as pd
import matplotlib; matplotlib.use("Agg"); import matplotlib.pyplot as plt

data = [
    {'judul_film': 'Train to Busan', 'genre': 'Drama', 'harga_tiket': 70000, 'jumlah_tiket': 121, 'tanggal': '2025-06-12', 'kota': 'Makassar', 'jam_tayang': '13:00', 'studio': 'C'},
    {'judul_film': 'The Avengers: Endgame', 'genre': 'Drama', 'harga_tiket': 55000, 'jumlah_tiket': 130, 'tanggal': '2025-06-15', 'kota': 'yogyakarta', 'jam_tayang': '22:00', 'studio': 'C'},
    {'judul_film': 'Spiderman: Homecoming', 'genre': 'Sci-Fi', 'harga_tiket': 70000, 'jumlah_tiket': 113, 'tanggal': '2025-06-12', 'kota': 'yogyakarta', 'jam_tayang': '16:00', 'studio': 'B'},
    {'judul_film': 'Doctor Strange: Multiverse of Madness', 'genre': 'Animasi', 'harga_tiket': 50000, 'jumlah_tiket': 74, 'tanggal': '2025-06-09', 'kota': 'SBY', 'jam_tayang': '19:30', 'studio': 'C'},
    {'judul_film': 'Frozen II', 'genre': 'Animasi', 'harga_tiket': 65000, 'jumlah_tiket': 132, 'tanggal': '2025-06-08', 'kota': 'MAKASSAR', 'jam_tayang': '18:00', 'studio': 'A'},
    {'judul_film': 'Avengers: Infinity War', 'genre': 'Drama', 'harga_tiket': 75000, 'jumlah_tiket': 133, 'tanggal': '2025-06-01', 'kota': 'Jakarta', 'jam_tayang': '18:00', 'studio': 'D'},
    {'judul_film': 'Train to Busan', 'genre': 'Komedi', 'harga_tiket': 55000, 'jumlah_tiket': 123, 'tanggal': '2025-06-01', 'kota': 'Bandung', 'jam_tayang': '19:30', 'studio': 'C'},
    {'judul_film': 'Black Panther', 'genre': 'Action', 'harga_tiket': 55000, 'jumlah_tiket': 81, 'tanggal': '2025-06-04', 'kota': 'YOGYAKARTA', 'jam_tayang': '16:00', 'studio': 'C'},
    {'judul_film': 'Captain America', 'genre': 'Komedi', 'harga_tiket': 80000, 'jumlah_tiket': 147, 'tanggal': '2025-06-09', 'kota': 'Surabaya', 'jam_tayang': '13:00', 'studio': 'D'},
    {'judul_film': 'Avengers: Infinity War', 'genre': 'Sci-Fi', 'harga_tiket': 55000, 'jumlah_tiket': 150, 'tanggal': '2025-06-02', 'kota': 'jakarta', 'jam_tayang': '13:00', 'studio': 'C'},
    {'judul_film': 'Inception', 'genre': 'Action', 'harga_tiket': 55000, 'jumlah_tiket': 130, 'tanggal': '2025-06-07', 'kota': 'Surabaya', 'jam_tayang': '14:30', 'studio': 'A'},
    {'judul_film': 'Joker', 'genre': 'Animasi', 'harga_tiket': 75000, 'jumlah_tiket': 80, 'tanggal': '2025-06-14', 'kota': 'MEDAN', 'jam_tayang': '13:00', 'studio': 'A'},
    {'judul_film': 'Iron Man', 'genre': 'Animasi', 'harga_tiket': 65000, 'jumlah_tiket': 67, 'tanggal': '2025-06-01', 'kota': 'makassar', 'jam_tayang': '18:00', 'studio': 'C'},
    {'judul_film': 'Captain America', 'genre': 'Horor', 'harga_tiket': 80000, 'jumlah_tiket': 140, 'tanggal': '2025-06-15', 'kota': 'Semarang', 'jam_tayang': '20:00', 'studio': 'B'},
    {'judul_film': 'Frozen II', 'genre': 'Sci-Fi', 'harga_tiket': 70000, 'jumlah_tiket': 65, 'tanggal': '2025-06-12', 'kota': 'JOGJA', 'jam_tayang': '20:00', 'studio': 'B'},
    {'judul_film': 'Spiderman: No Way Home', 'genre': 'Komedi', 'harga_tiket': 80000, 'jumlah_tiket': 118, 'tanggal': '2025-06-12', 'kota': 'semarang', 'jam_tayang': '19:30', 'studio': 'D'},
    {'judul_film': 'Captain America', 'genre': 'Drama', 'harga_tiket': 70000, 'jumlah_tiket': 131, 'tanggal': '2025-06-14', 'kota': 'JKT', 'jam_tayang': '16:00', 'studio': 'B'},
    {'judul_film': 'Conjuring The Devil Made Me Do It', 'genre': 'Action', 'harga_tiket': 70000, 'jumlah_tiket': 103, 'tanggal': '2025-06-06', 'kota': 'Medan', 'jam_tayang': '14:30', 'studio': 'B'},
    {'judul_film': 'Conjuring The Devil Made Me Do It', 'genre': 'Sci-Fi', 'harga_tiket': 75000, 'jumlah_tiket': 63, 'tanggal': '2025-06-10', 'kota': 'MEDAN', 'jam_tayang': '14:30', 'studio': 'B'},
    {'judul_film': 'Doctor Strange: Multiverse of Madness', 'genre': 'Action', 'harga_tiket': 80000, 'jumlah_tiket': 66, 'tanggal': '2025-06-01', 'kota': 'MEDAN', 'jam_tayang': '19:30', 'studio': 'D'},
    {'judul_film': 'Inception', 'genre': 'Action', 'harga_tiket': 60000, 'jumlah_tiket': 131, 'tanggal': '2025-06-08', 'kota': 'SURABAYA', 'jam_tayang': '13:00', 'studio': 'D'},
    {'judul_film': 'Spiderman: No Way Home', 'genre': 'Sci-Fi', 'harga_tiket': 70000, 'jumlah_tiket': 66, 'tanggal': '2025-06-02', 'kota': 'Jakarta', 'jam_tayang': '19:30', 'studio': 'D'},
    {'judul_film': 'Parasite', 'genre': 'Komedi', 'harga_tiket': 75000, 'jumlah_tiket': 85, 'tanggal': '2025-06-12', 'kota': 'SEMARANG', 'jam_tayang': '21:00', 'studio': 'D'},
    {'judul_film': 'Frozen II', 'genre': 'Drama', 'harga_tiket': 75000, 'jumlah_tiket': 79, 'tanggal': '2025-06-11', 'kota': 'SURABAYA', 'jam_tayang': '20:00', 'studio': 'A'},
    {'judul_film': 'Avenger Boy', 'genre': 'Komedi', 'harga_tiket': 55000, 'jumlah_tiket': 111, 'tanggal': '2025-06-08', 'kota': 'SMG', 'jam_tayang': '20:00', 'studio': 'A'},
    {'judul_film': 'Black Panther', 'genre': 'Action', 'harga_tiket': 60000, 'jumlah_tiket': 105, 'tanggal': '2025-06-14', 'kota': 'SBY', 'jam_tayang': '18:00', 'studio': 'C'},
    {'judul_film': 'Your Name', 'genre': 'Komedi', 'harga_tiket': 65000, 'jumlah_tiket': 77, 'tanggal': '2025-06-15', 'kota': 'SEMARANG', 'jam_tayang': '20:00', 'studio': 'B'},
    {'judul_film': 'Spiderman: Far From Home', 'genre': 'Drama', 'harga_tiket': 65000, 'jumlah_tiket': 133, 'tanggal': '2025-06-06', 'kota': 'Yogyakarta', 'jam_tayang': '13:00', 'studio': 'B'},
    {'judul_film': 'Spiderman: No Way Home', 'genre': 'Sci-Fi', 'harga_tiket': 80000, 'jumlah_tiket': 104, 'tanggal': '2025-06-13', 'kota': 'MAKASSAR', 'jam_tayang': '14:30', 'studio': 'A'},
    {'judul_film': 'Laskar Pelangi', 'genre': 'Komedi', 'harga_tiket': 60000, 'jumlah_tiket': 139, 'tanggal': '2025-06-10', 'kota': 'SBY', 'jam_tayang': '19:30', 'studio': 'C'},
    {'judul_film': 'Conjuring The Devil Made Me Do It', 'genre': 'Horor', 'harga_tiket': 80000, 'jumlah_tiket': 91, 'tanggal': '2025-06-15', 'kota': 'semarang', 'jam_tayang': '18:00', 'studio': 'A'},
    {'judul_film': 'Inception', 'genre': 'Komedi', 'harga_tiket': 75000, 'jumlah_tiket': 78, 'tanggal': '2025-06-05', 'kota': 'MEDAN', 'jam_tayang': '22:00', 'studio': 'A'},
    {'judul_film': 'Kimi no Na wa', 'genre': 'Komedi', 'harga_tiket': 80000, 'jumlah_tiket': 66, 'tanggal': '2025-06-04', 'kota': 'YOGYAKARTA', 'jam_tayang': '14:30', 'studio': 'D'},
    {'judul_film': 'Frozen II', 'genre': 'Komedi', 'harga_tiket': 50000, 'jumlah_tiket': 61, 'tanggal': '2025-06-11', 'kota': 'Bandung', 'jam_tayang': '21:00', 'studio': 'C'},
    {'judul_film': 'Avenger Boy', 'genre': 'Action', 'harga_tiket': 65000, 'jumlah_tiket': 85, 'tanggal': '2025-06-04', 'kota': 'makassar', 'jam_tayang': '19:30', 'studio': 'A'},
    {'judul_film': 'Your Name', 'genre': 'Komedi', 'harga_tiket': 65000, 'jumlah_tiket': 135, 'tanggal': '2025-06-04', 'kota': 'JKT', 'jam_tayang': '20:00', 'studio': 'A'},
    {'judul_film': 'The Avengers Assemble', 'genre': 'Horor', 'harga_tiket': 75000, 'jumlah_tiket': 132, 'tanggal': '2025-06-07', 'kota': 'Makassar', 'jam_tayang': '16:00', 'studio': 'A'},
    {'judul_film': 'Parasite', 'genre': 'Horor', 'harga_tiket': 80000, 'jumlah_tiket': 74, 'tanggal': '2025-06-08', 'kota': 'MAKASSAR', 'jam_tayang': '21:00', 'studio': 'B'},
    {'judul_film': 'The Batman', 'genre': 'Komedi', 'harga_tiket': 70000, 'jumlah_tiket': 62, 'tanggal': '2025-06-10', 'kota': 'MEDAN', 'jam_tayang': '13:00', 'studio': 'A'},
    {'judul_film': 'Spiderman: Far From Home', 'genre': 'Animasi', 'harga_tiket': 60000, 'jumlah_tiket': 60, 'tanggal': '2025-06-08', 'kota': 'Jakarta', 'jam_tayang': '14:30', 'studio': 'B'},
    {'judul_film': 'The Batman', 'genre': 'Animasi', 'harga_tiket': 75000, 'jumlah_tiket': 94, 'tanggal': '2025-06-02', 'kota': 'SMG', 'jam_tayang': '14:30', 'studio': 'C'},
    {'judul_film': 'Captain America', 'genre': 'Action', 'harga_tiket': 60000, 'jumlah_tiket': 116, 'tanggal': '2025-06-12', 'kota': 'jakarta', 'jam_tayang': '19:30', 'studio': 'B'},
    {'judul_film': 'Laskar Pelangi', 'genre': 'Drama', 'harga_tiket': 50000, 'jumlah_tiket': 75, 'tanggal': '2025-06-02', 'kota': 'SMG', 'jam_tayang': '14:30', 'studio': 'C'},
    {'judul_film': 'Frozen II', 'genre': 'Horor', 'harga_tiket': 80000, 'jumlah_tiket': 123, 'tanggal': '2025-06-02', 'kota': 'JOGJA', 'jam_tayang': '20:00', 'studio': 'B'},
    {'judul_film': 'Black Panther', 'genre': 'Komedi', 'harga_tiket': 80000, 'jumlah_tiket': 93, 'tanggal': '2025-06-04', 'kota': 'surabaya', 'jam_tayang': '16:00', 'studio': 'D'},
    {'judul_film': 'Conjuring The Devil Made Me Do It', 'genre': 'Action', 'harga_tiket': 50000, 'jumlah_tiket': 68, 'tanggal': '2025-06-07', 'kota': 'SBY', 'jam_tayang': '22:00', 'studio': 'D'},
    {'judul_film': 'Interstellar', 'genre': 'Komedi', 'harga_tiket': 60000, 'jumlah_tiket': 142, 'tanggal': '2025-06-15', 'kota': 'Makassar', 'jam_tayang': '20:00', 'studio': 'A'},
    {'judul_film': 'Your Name', 'genre': 'Sci-Fi', 'harga_tiket': 55000, 'jumlah_tiket': 136, 'tanggal': '2025-06-01', 'kota': 'bandung', 'jam_tayang': '22:00', 'studio': 'B'},
    {'judul_film': 'Parasite', 'genre': 'Action', 'harga_tiket': 60000, 'jumlah_tiket': 103, 'tanggal': '2025-06-07', 'kota': 'JAKARTA', 'jam_tayang': '22:00', 'studio': 'C'},
    {'judul_film': 'Spiderman: Far From Home', 'genre': 'Komedi', 'harga_tiket': 80000, 'jumlah_tiket': 90, 'tanggal': '2025-06-02', 'kota': 'SBY', 'jam_tayang': '20:00', 'studio': 'C'},
    {'judul_film': 'Black Panther', 'genre': 'Sci-Fi', 'harga_tiket': 80000, 'jumlah_tiket': 103, 'tanggal': '2025-06-11', 'kota': 'yogyakarta', 'jam_tayang': '20:00', 'studio': 'C'},
    {'judul_film': 'Laskar Pelangi', 'genre': 'Komedi', 'harga_tiket': 65000, 'jumlah_tiket': 71, 'tanggal': '2025-06-07', 'kota': 'jakarta', 'jam_tayang': '14:30', 'studio': 'A'},
    {'judul_film': 'Conjuring The Devil Made Me Do It', 'genre': 'Drama', 'harga_tiket': 60000, 'jumlah_tiket': 108, 'tanggal': '2025-06-11', 'kota': 'SEMARANG', 'jam_tayang': '19:30', 'studio': 'B'},
    {'judul_film': 'Conjuring The Devil Made Me Do It', 'genre': 'Horor', 'harga_tiket': 60000, 'jumlah_tiket': 136, 'tanggal': '2025-06-01', 'kota': 'SMG', 'jam_tayang': '16:00', 'studio': 'D'},
    {'judul_film': 'The Avengers Assemble', 'genre': 'Drama', 'harga_tiket': 75000, 'jumlah_tiket': 80, 'tanggal': '2025-06-13', 'kota': 'Surabaya', 'jam_tayang': '14:30', 'studio': 'B'},
    {'judul_film': 'Spiderman: Homecoming', 'genre': 'Action', 'harga_tiket': 65000, 'jumlah_tiket': 100, 'tanggal': '2025-06-15', 'kota': 'SMG', 'jam_tayang': '18:00', 'studio': 'C'},
    {'judul_film': 'Toy Story 4', 'genre': 'Action', 'harga_tiket': 60000, 'jumlah_tiket': 73, 'tanggal': '2025-06-06', 'kota': 'bandung', 'jam_tayang': '14:30', 'studio': 'B'},
    {'judul_film': 'Frozen II', 'genre': 'Sci-Fi', 'harga_tiket': 55000, 'jumlah_tiket': 76, 'tanggal': '2025-06-01', 'kota': 'bandung', 'jam_tayang': '22:00', 'studio': 'A'},
    {'judul_film': 'Frozen II', 'genre': 'Komedi', 'harga_tiket': 60000, 'jumlah_tiket': 82, 'tanggal': '2025-06-02', 'kota': 'BANDUNG', 'jam_tayang': '21:00', 'studio': 'A'},
    {'judul_film': 'Doctor Strange: Multiverse of Madness', 'genre': 'Drama', 'harga_tiket': 55000, 'jumlah_tiket': 107, 'tanggal': '2025-06-05', 'kota': 'YOGYAKARTA', 'jam_tayang': '21:00', 'studio': 'D'}
]

df = pd.DataFrame(data)
df["pendapatan"] = df["harga_tiket"] * df["jumlah_tiket"]          # revenue of each screening

# Soal 1: total revenue per genre
pendapatan_per_genre = df.groupby("genre")["pendapatan"].sum().sort_values(ascending=False).to_dict()
print("Soal 1:", pendapatan_per_genre)

# Soal 2: mean ticket price per genre (mean over screenings)
rata_rata_harga_per_genre = df.groupby("genre")["harga_tiket"].mean().round(2).to_dict()
print("Soal 2:", rata_rata_harga_per_genre)

# Soal 3: date with the most tickets, all films together
tiket_per_tanggal = df.groupby("tanggal")["jumlah_tiket"].sum()
tanggal_terbanyak = tiket_per_tanggal.idxmax()
print("Soal 3:", tanggal_terbanyak, tiket_per_tanggal.max())

# Soal 4: the city column is dirty (case, abbreviations): normalise BEFORE grouping
ALIAS = {"sby": "Surabaya", "surabaya": "Surabaya", "smg": "Semarang", "semarang": "Semarang",
         "jkt": "Jakarta", "jakarta": "Jakarta", "jogja": "Yogyakarta", "yogyakarta": "Yogyakarta",
         "makassar": "Makassar", "medan": "Medan", "bandung": "Bandung"}
df["kota_bersih"] = df["kota"].str.strip().str.lower().map(ALIAS)
assert df["kota_bersih"].notna().all()
judul_unik = df.groupby("kota_bersih")["judul_film"].nunique().sort_values(ascending=False)
kota_variasi_tertinggi = judul_unik[judul_unik == judul_unik.max()].index.tolist()
print("Soal 4 (raw column, WRONG):", df.groupby("kota")["judul_film"].nunique().nlargest(3).to_dict())
print("Soal 4:", kota_variasi_tertinggi, judul_unik.max())

# Soal 5: revenue per film
pendapatan_per_film = df.groupby("judul_film")["pendapatan"].sum().sort_values(ascending=False)
film_teruntung = pendapatan_per_film.idxmax()
print("Soal 5:", film_teruntung, pendapatan_per_film.max())

# Bonus: the genre label is not a property of the film
genres_per_film = df.groupby("judul_film")["genre"].unique()
print("Bonus:", {f: list(g) for f, g in genres_per_film.items() if len(g) >= 4})
print("Bonus: films with more than one genre:", (genres_per_film.str.len() > 1).sum(), "of", len(genres_per_film))

# Figure
fig, ax = plt.subplots(1, 2, figsize=(13, 4.8), gridspec_kw={"width_ratios": [1.25, 1]})
raw = df.groupby("kota")["judul_film"].nunique().sort_values()
ax[0].barh(raw.index, raw.values, color="#bbb")
for i, (k, v) in enumerate(raw.items()): ax[0].text(v + .1, i, str(v), va="center", fontsize=8)
ax[0].set_title("Raw 'kota' column: 24 spellings of 7 cities"); ax[0].set_xlabel("unique film titles")
ax[0].tick_params(axis="y", labelsize=8)
cl = judul_unik.sort_values()
ax[1].barh(cl.index, cl.values, color=["#c0392b" if v == cl.max() else "#2471a3" for v in cl.values])
for i, v in enumerate(cl.values): ax[1].text(v + .1, i, str(v), va="center")
ax[1].set_title("After normalising the city names"); ax[1].set_xlabel("unique film titles")
plt.tight_layout(); plt.savefig("figures/city_cleaning.png", dpi=150); plt.close()