Pandas-Jupyter labor

2019. március 26.

Név (neptun):

YOUR ANSWER HERE

A labor célja egy rövid bevezetőt adni a manapság népszerű "data science" Python eszközeibe. A labor feladatai előtt mindenképp meg kell ismerkedni a Python nyelv alapjaival. A laborhoz tartozó rövid magyar Python bevezetőt itt találod.

A pandashoz egy rövid magyar bevezető itt.

A labort összeállította: Ács Judit

A labor menete

A labor teljes beadandó anyaga ez a notebook. A kérdések sorszámozva vannak és Q-val kezdődnek: Q1.1-Q5.5-ig. Néhány szorgalmi kérdés is van, ezekkel plusz pontot lehet szerezni a maximálison felül. Minden kiskérdés 2 pontot ér, a 25 kérdés összesen 50 pont. A jegyek a következőképpen alakulnak:

pontszám jegy
40+ 5
30+ 4
20+ 3
10+ 2
9- 1

A feladatokhoz tartoznak tesztek, amiket a megoldás elkészítése előtt érdemes elolvasni.

FIGYELEM! A vizualizációt nem tartalmazó feladatok javítása automatikusan történik. A notebookban szereplő tesztek a visszatérési értékek típusát ellenőrzik, a válaszok helyességét rejtett tesztek ellenőrzik. A teszteket tartalmazó cellákat nem lehet módosítani. A kitöltendő helyek YOUR CODE HERE commenttel vannak jelölve (az exception dobását értelemszerűen törölni kell).

Beadás

Amennyiben az órán nem sikerült befejezned, otthon folytathatod a munkát és a labor hetének végéig (vasárnap éjfél) feltöltheted. Az Anaconda Python disztribúció tartalmazza a laboranyaghoz szükséges összes csomagot, beleértve a jupytert.

  1. Mielőtt feltöltöd, győződj meg róla, hogy újraindított kernellel exception nélkül lefut és azokat eredményeket adja, amiket vártál. Ezt a Kernel->Restart & Run All opcióval teheted meg. Ha nem minden feladatot oldasz meg, akkor a NotImplementedError-ok miatt nem fog végigfutni, de kézzel le tudod futtatni a cellákat (Shift+Enter lefuttatja és a következőre lép).
  2. Ügyelj arra, hogy ne maradjanak hosszú táblázatok kiírva. Helyette használd a head függvényt.

A laborhoz külön jegyzőkönyvet nem kell készíteni, ezt a notebookot kell vasárnap éjfélig az AUT portálra feltölteni NEPTUN.ipynb néven. A fájlrendszerben pandas_labor.ipynb néven megtalálod a notebookot abban a könyvtárban, ahonnan indítottad a jupytert. Az AUT portálra .zip-be csomagolva tudod feltölteni.


In [ ]:
import pandas as pd  # konvenció szerint pd aliast használunk
%matplotlib inline
import matplotlib
import numpy as np

# tegyük szebbé a grafikonokat
matplotlib.style.use('ggplot')
matplotlib.pyplot.rcParams['figure.figsize'] = (15, 3)
matplotlib.pyplot.rcParams['font.family'] = 'sans-serif'

A MovieLens adatsorral fogunk dolgozni, de először le kell töltenünk. http://grouplens.org/datasets/movielens/

Csak akkor töltjük le a fájlt, ha még nem létezik.


In [ ]:
import os

data_dir = os.getenv("MOVIELENS")
if data_dir is None:
    data_dir = ""

ml_path = os.path.join(data_dir, "ml.zip")

if not os.path.exists(ml_path):
    print("Download data")
    import urllib
    u = urllib.request.URLopener()
    u.retrieve("http://files.grouplens.org/datasets/movielens/ml-100k.zip", ml_path)
    print("Data downloaded")

Kicsomagoljuk:


In [ ]:
unzip_path = os.path.join(data_dir, "ml-100k")

if not os.path.exists(unzip_path):
    print("Extracting data")
    from zipfile import ZipFile
    with ZipFile(ml_path) as myzip:
        myzip.extractall(data_dir)
    print("Data extraction done")
        
data_dir = unzip_path

Adat betöltése és normalizálása

A pd.read_table függvény táblázatos adatok betöltésére alkalmas. Több tucat paraméterrel rendelkezik, de csak egy kötelező paramétere van: a fájl, amit beolvasunk. A karakterkódolást is meg kell adnunk, mert a fájl nem az alapértelmezett (utf-8) kódolást használja, hanem az ISO-8859-1-et, vagy köznéven a latin1-et.


In [ ]:
# df = pd.read_table("ml-100k/u.item")  # UnicodeDecodeErrort kapunk, mert rossz dekódert használ
df = pd.read_table(os.path.join(data_dir, "u.item"), encoding="latin1")
df.head()

Ez még elég rosszul néz ki. Hogyan tudnánk javítani?

  1. Rossz szeparátort használt a függvény (tab az alapértelmezett). A fájlban | a szeparátor. Ezt a sep paraméterrel tudjuk megadni.
  2. A fájl első sora került az oszlopnevek helyére. Az oszlopok valódi nevei a README fájlból derülnek ki, amit kézzel megadhatjuk a read_table-nek a names paraméterben.
  3. A read_table automatikusan generált egy id-t minden sornak, azonban az adatfájlban a filmek már rendelkeznek egy egyedi azonosítóval (movie_id), használjuk ezt a DataFrame indexeként (index_col paraméter). Célszerű szóköz nélküli, kisbetűs oszlopneveket használni, mert akkor attribútumként is elérjük őket (df.release_date).

In [ ]:
column_names = [
    "movie_id", "title", "release_date", "video_release_date", "imdb_url", "unknown", "action", "adventure", "animation",
    "children", "comedy", "crime", "documentary", "drama", "fantasy", "film_noir", "horror", "musical", "mystery",
    "romance", "sci_fi", "thriller", "war", "western"]

In [ ]:
df = pd.read_table(
    os.path.join(data_dir, "u.item"), sep="|",
    names=column_names, encoding="latin1", index_col='movie_id')
df.head()

Két oszlop is van, amik dátumot jelölnek: release_date, video_release_date. A pandas parszolni tudja a dátumokat többféle népszerű formátumban, ehhez csak a parse_dates paraméterben kell megadnunk a dátumot tartalmazó oszlopokat. Figyeljük meg, hogy ahol nincs dátum, az Nan (not a number)-ről NaT-ra (not a time) változik.


In [ ]:
df = pd.read_table(os.path.join(data_dir, "u.item"), sep="|",
                   names=column_names, encoding="latin1",
                   parse_dates=[2,3], index_col='movie_id')
df.head()

Még mindig nem tökéletes, hiszen a filmek címei után szerepel az évszám zárójelben, ami egyrészt redundáns, másrészt zaj. Tüntessük el!

A szokásos str műveletek egy része elérhető DataSeries objektumokra is (minden elemre végrehajtja). A függvényeket az str névtérben találjuk.


In [ ]:
df.title.str

Egy reguláris kifejezéssel eltüntetjük a két zárójel közti részt, majd eltávolítjuk az ott maradt whitespace-eket (a strip függvény a stringek elejéről és végéről is eltávolítja). Végül adjuk értékül a régi title oszlopnak a kezdő és záró whitespace-ektől megfosztott változatát.


In [ ]:
df.title = df.title.str.replace(r'\(.*\)', '').str.strip()

In [ ]:
df.head()

A video_release_date mező az első néhány sorban csak érvénytelen mezőket tartalmaz. Vajon igaz ez az egész DataFrame-re? Listázzuk ki azokat a mezőket, ahol nem NaT a video_release_date értéke, vagyis érvénytelen dátum.


In [ ]:
df[df.video_release_date.notnull()]

Nincs ilyen mező, ezért elhagyhatjuk az oszlopot.


In [ ]:
df = df.drop('video_release_date', axis=1)
df.head()

Van egy unknown oszlop, ettől is szabaduljunk meg!


In [ ]:
df = df.drop('unknown', axis=1)

Adatok felszínes vizsgálata

Nézzük meg, hogy milyen információkat tudhatunk könnyedén meg a DataFrame-ről.

A describe függvény oszloponként szolgáltat alapvető infomációkkal: darabszám, átlag, szórás stb. Mivel a legtöbb mező bináris, most nem tudunk meg sok hasznos információt a mezőkről.


In [ ]:
df.describe()

Átlag, szórás, variancia stb.

Egyenként is lekérdezhetőek:

  1. count()
  2. átlag: mean()
  3. szórás: std()
  4. variancia: var()
  5. 50% kvantilis: quantile(.5)
  6. min, max

In [ ]:
df.quantile(.9).head()

Egyszerű lekérdezések

Melyik filmek jelentek meg 1956-ban?


In [ ]:
df[df.release_date.dt.year == 1956]

Melyik filmek jelentek meg a 80-as években?


In [ ]:
d = df[(df.release_date.dt.year >=  1980) & (df.release_date.dt.year < 1990)]
len(d)

107 film jelent meg a 80-as években, ezt már nem praktikus kiíni. Nézzük meg csak az első 3-at.


In [ ]:
d.head(3)

A megjelenítési év legyen külön oszlop

Többször fogjuk még használni a megjelenési évet, ezért praktikus külön év oszlopot létrehozni.

A DateTime mezőhöz használható metódusok és attribútumok a dt névtérben vannak, így tudjuk minden oszlopra egyszerre meghívni. Az eredményt egy új oszlopban tároljuk.


In [ ]:
df['year'] = df.release_date.dt.year

Mikor jelentek meg a Die Hard filmek?


In [ ]:
df[df.title == 'Die Hard']

Sajnos csak teljes egyezésre tudunk így szűrni.

A szöveges mezőkre a pandas nyújt egy csomó műveletet, amik az str névtérben vannak (ahogy a dátum mezőkre a dt-ben voltak).


In [ ]:
df[df.title.str.contains('Die Hard')]

A Die Hard 4 és 5 hiányzik. Kilógnának az adatsorból? Nézzük meg még egyszer, hogy mikori filmek szerepelnek.


In [ ]:
df.release_date.describe()

A Die Hard 4 és 5 2007-ben, illetve 2013-ban jelentek meg, ezért nem szerepelnek az adatban.

Melyik filmek tartoznak egyszerre az akció és romantikus kategóriába?


In [ ]:
d = df[(df.action==1) & (df.romance==1)]
print(len(d))
d.head()

Melyik filmek tartoznak az akció VAGY a romantikus kategóriába?

Itt a Boole vagyra gondolunk.


In [ ]:
d = df[(df.action==1) | (df.romance==1)]
print(len(d))
d.head()

1. feladat: egyszerű lekérdezések

Q1.1. Hány akciófilm jelent meg 1985 előtt, illetve 1985-ben vagy később?


In [ ]:
def count_movies_before_1985(df):
    # YOUR CODE HERE
    raise NotImplementedError()
    
def count_movies_after_1984(df):
    # YOUR CODE HERE
    raise NotImplementedError()

In [ ]:
before = count_movies_before_1985(df)
print(before)
assert type(before) == int

after = count_movies_after_1984(df)
print(after)
assert type(after) == int

Q1.2. Létezik-e gyerekeknek szóló thriller? Keress egy példát rá és térj vissza a film címével.


In [ ]:
def child_thriller(df):
    # YOUR CODE HERE
    raise NotImplementedError()

In [ ]:
title = child_thriller(df)
assert type(title) == str

Q1.3. Hány filmnek hosszabb a címe, mint 30 karakter?


In [ ]:
def long_titles(df):
    # YOUR CODE HERE
    raise NotImplementedError()

In [ ]:
title_cnt = long_titles(df)
assert type(title_cnt) == int

Q1.4. Mi a legrégebbi és a legújabb film címe?

A megjelenésnek nem csak éve van!


In [ ]:
def oldest_movie(df):
    # YOUR CODE HERE
    raise NotImplementedError()

def newest_movie(df):
    # YOUR CODE HERE
    raise NotImplementedError()

In [ ]:
oldest = oldest_movie(df)
newest = newest_movie(df)

assert type(oldest) == str
assert type(newest) == str

Q1.5. Melyik a legújabb sci-fi?


In [ ]:
def newest_scifi(df):
    # YOUR CODE HERE
    raise NotImplementedError()

In [ ]:
newest = newest_scifi(df)
assert type(newest) == str

Csoportosítás és vizualizáció

Hány filmet adtak ki évente?

A kérdést két lépésben tudjuk megválaszolni:

  1. csoportosítás évenként
  2. összesítés 1-1 évre

In [ ]:
df.groupby('year').size().plot()

Vonaldiagram az alapértelmezett, de oszlopdiagramként informatívabb lenne.


In [ ]:
df.groupby('year').size().plot(kind='bar')

Lásztik, hogy a 80-as évek végén nőtt meg a kiadott filmek száma, kicsit közelítsünk rá. Ehhez először szűrni fogjuk a 1985 utáni filmeket, majd csoportosítva ábrázolni.


In [ ]:
d = df[df.year > 1985]
d.groupby('year').size().plot(kind='bar')
# df[df.year > 1985].groupby('year').size().plot(kind='bar')  # vagy egy sorban

Groupby tetszőleges feltétel szerint

Nem csak egy kategóriaértékű oszlop szerint csoportosíthatunk, hanem tetszőleges kifejezés szerint. Ezt kihasználva fogunk évtizedenként csoportosítani. A groupby-nak bármilyen kifejezést megadhatunk, ami diszkrét értékekre képezi le a sorokat, tehát véges sok csoport egyikébe helyezi (mint egy hash függvény).

Az évtizedet úgy kaphatjuk meg, ha az évet 10-zel osztjuk és csak az egészrészt tartjuk meg, hiszen 1983/10 és 1984/10 egészrésze ugyanúgy 198. Használjuk a Python egészosztás operátorát (//).


In [ ]:
d = df.groupby(df.year // 10 * 10)
d.groups.keys()  # létrejött csoportok listázása

In [ ]:
d.size().plot(kind='bar')

2. feladat: csoportosítás és vizualizáció

Q2.1. Csoportosítsd vígjátékokat (comedy) évenként. Ábrázold oszlopdiagramon hány vígjátékot adtak ki évente.


In [ ]:
def comedy_by_year(df):
    # YOUR CODE HERE
    raise NotImplementedError()

In [ ]:
c = comedy_by_year(df)
assert type(c) == pd.core.groupby.DataFrameGroupBy

Ábrázold.


In [ ]:
# YOUR CODE HERE
raise NotImplementedError()

Gyerekfilmet vagy krimit adnak ki többet évtizedenként?


In [ ]:
col1 = 'children'
col2 = 'crime'
d = df[['year', col1, col2]].copy()
d['diff'] = d[col1] - d[col2]
d.groupby(d.year // 10 * 10).sum()

In [ ]:
d.groupby(d.year // 10 * 10).sum().plot(y='diff', kind='bar')

A 90-es években több filmet adtak ki, mint előtte összesen, nézzük meg azt az évtizedet közelebbről!

Q2.2. Mennyivel adtak ki több gyerekfilmet, mint krimit évente a 90-es években? Ábrázold.

Először a szűrt groupby-t készítsd el az imént létrhozott d DataFrame-ből.


In [ ]:
def groupby_nineties(d):
    # YOUR CODE HERE
    raise NotImplementedError()

In [ ]:
nineties = groupby_nineties(d)
assert type(nineties) == pd.core.groupby.DataFrameGroupBy

# a diff oszlop szerepel
assert 'diff' in nineties.sum()

Ábrázold.


In [ ]:
# YOUR CODE HERE
raise NotImplementedError()

Q2.3. Ábrázold a kiadási napok (hónap napjai) eloszlását egy tortadiagramon!

Tortadiagramot a plot függvény kind="pie" argumentumával tudsz készíteni.

A tortadiagramhoz érdemes megváltoztatni a diagram képarányát, amit a plot függvény figsize paraméterének megadásával tehetsz meg. figsize=(10,10). Százalékokat a autopct="%.0lf%%" opcióval lehet a diagramra írni.

A tortadiagramot szebbé teheted másik colormap választásával: dokumentáció és a colormapek listája.


In [ ]:
def groupby_release_day(df):
    # YOUR CODE HERE
    raise NotImplementedError()

In [ ]:
by_day = groupby_release_day(df)
assert type(by_day) == pd.core.groupby.DataFrameGroupBy

# legfeljebb 31 napos egy hónap
assert len(by_day) < 32

# nehogy a hét napjai szerint csoportosítsunk
assert len(by_day) > 7

Ábrázold.


In [ ]:
# YOUR CODE HERE
raise NotImplementedError()

Q2.4. Hagyományos lexikont szeretnénk készíteni a filmekből. Melyik kezdőbetű hányszor fordul elő a filmek címében? Ábrázold tortadiagramon.

Csoportosítsd a filmeket kezdőbetű szerint.


In [ ]:
def groupby_initial_letter(df):
    # YOUR CODE HERE
    raise NotImplementedError()

In [ ]:
initial = groupby_initial_letter(df)

assert type(initial) == pd.core.groupby.DataFrameGroupBy

Ábrázold.


In [ ]:
# YOUR CODE HERE
raise NotImplementedError()

*Q2.5. Írj függvényt, ami több oszlop mentén csoportosít és visszaadja a legnagyobb csoportot.

Tipp: a GroupBy objektum get_group függvénye visszaad egy csoportot.


In [ ]:
def get_largest_group(df, groupby_columns):
    # YOUR CODE HERE
    raise NotImplementedError()

In [ ]:
genres = ["drama"]
drama_largest = get_largest_group(df, genres)

assert type(drama_largest) == pd.DataFrame
assert len(drama_largest) == 957

genres = ["drama", "comedy"]
both_largest = get_largest_group(df, genres)

# a csoportban minden film comedy es drama cimkeje azonos
assert both_largest[["comedy", "drama"]].nunique().loc["comedy"] == 1
assert both_largest[["comedy", "drama"]].nunique().loc["drama"] == 1
print(both_largest.shape)

Több DataFrame kezelése, pd.merge

Az adathalmaz lényegi része a 100000 értékelés, amit az u.data fájlból tudunk beolvasni. A README-ből kiolvashatjuk a fájl oszlopait.


In [ ]:
cols = ['user', 'movie_id', 'rating', 'timestamp']
ratings = pd.read_table(os.path.join(data_dir, "u.data"), names=cols)

In [ ]:
ratings.head()

A timestamp oszlop Unix timestampeket tartalmaz, konvertáljuk DateTime-má.


In [ ]:
ratings['timestamp'] = pd.to_datetime(ratings.timestamp, unit='s')
ratings.head()

Merge a film táblával

Mivel már több DataFrame-mel dolgozunk, érdemes a filmeket tartalmazó táblának beszédesebb nevet adni.


In [ ]:
movies = df

Felülírjuk a ratings táblát:


In [ ]:
ratings = pd.merge(ratings, movies, left_on='movie_id', right_index=True)
ratings.head()

Hány értékelés érkezett a film megjelenése előtt?


In [ ]:
len(ratings[ratings.timestamp <= ratings.release_date])

Hogy oszlik meg ez a szám a filmek között?


In [ ]:
ratings[ratings.timestamp <= ratings.release_date].title.value_counts()

3. feladat: merge

Q3.1. Hány film kapott legalább egyszer 4 fölötti értékelést?

VIGYÁZAT! A filmek címe nem feltétlenül egyedi.


In [ ]:
def count_greater_than_4(ratings):
    # YOUR CODE HERE
    raise NotImplementedError()

In [ ]:
greater = count_greater_than_4(ratings)

assert type(greater) == int
assert greater != 1160  # titles are NOT UNIQUE

Hisztogram készítése az egyes értékelésekről

Hisztogram készítésére (melyik érték hányszor szerepelt), a hist függvény áll rendelkezésünkre:


In [ ]:
ratings.hist('rating', bins=5)

Q3.2. Ábrázold hisztogramon az 1960 előtti krimik értékeléseit!


In [ ]:
def filter_old_crime_movies(ratings):
    # YOUR CODE HERE
    raise NotImplementedError()

In [ ]:
old_crime_movies = filter_old_crime_movies(ratings)

assert type(old_crime_movies) == pd.DataFrame

Ábrázold.


In [ ]:
# YOUR CODE HERE
raise NotImplementedError()

Q3.3. Mi az értékelések átlaga évtizedenként (film megjelenési éve)?

Figyelj arra, hogy csak annyi adat szerepeljen az összesítésben, amennyit a feladat kér. Az indexek legyenek az évtizedek kezdőévei.


In [ ]:
def rating_mean_by_decade(ratings):
    # YOUR CODE HERE
    raise NotImplementedError()

In [ ]:
decade_mean = rating_mean_by_decade(ratings)

# csak az ertekeles oszlop atalga erdekel minket, nem az egesz DataFrame-e
assert not type(decade_mean) == pd.DataFrame
assert type(decade_mean) == pd.Series
assert 1920 in decade_mean.index
assert 1921 not in decade_mean.index

Q3.4. Az értékelésekhez tartozik egy timestamp. Mi az értékelések átlaga a hét napjaira lebontva?

Tehát melyik napon jószívűbbek az emberek?

Tipp: érdemes körbenézni a dátummezőkhöz tartozó dt névtérben.


In [ ]:
def rating_mean_by_weekday(ratings):
    # YOUR CODE HERE
    raise NotImplementedError()

In [ ]:
weekday_mean = rating_mean_by_weekday(ratings)

assert type(weekday_mean) == pd.Series
assert type(weekday_mean) != pd.DataFrame  # csak egy oszlop kell

Q3.5. Melyik hónapban mennyi a kalandfilmek (adventure) értékeléseinek szórása?

Vigyázat, a szórás és a variancia nem azonos!


In [ ]:
def adventure_monthly_std(ratings):
    # YOUR CODE HERE
    raise NotImplementedError()

In [ ]:
adventure = adventure_monthly_std(ratings)
assert type(adventure) == pd.Series
assert type(adventure) != pd.DataFrame

# legfeljebb 12 különböző hónapban érkezhettek értékelések
assert len(adventure) <= 12

4. feladat: Users DataFrame

Q4.1 Olvasd be a u.user fájlt egy users nevű DataFrame-be!

Segítségképpen az oszlopok: user_id, age, gender, occupation, zip. A user_id oszlop legyen a DataFrame indexe.


In [ ]:
# users = ...
# YOUR CODE HERE
raise NotImplementedError()

In [ ]:
assert type(users) == pd.DataFrame

# user_id starts from 1
assert 0 not in users.index

Q4.2. Merge-öld a ratings táblát a users táblával. Őrizd meg az összes oszlopot.


In [ ]:
# ratings = ratings.merge...
# YOUR CODE HERE
raise NotImplementedError()

In [ ]:
assert type(ratings) == pd.DataFrame
assert ratings.shape == (100000, 30)

Q4.3. Korcsoportonként hány értékelést adtak le? 10 évet veszünk egy korcsoportnak, tehát 10-19, 20-29 stb. Ábrázold oszlopdiagramon.


In [ ]:
def by_age_group(ratings):
    # YOUR CODE HERE
    raise NotImplementedError()

In [ ]:
r = by_age_group(ratings)

assert type(r) == pd.Series
assert 20 in r

Ábrázold.


In [ ]:
# YOUR CODE HERE
raise NotImplementedError()

Q4.4. A nap melyik órájában értékelnek a programozók, illetve a marketingesek? Ábrázold két tortadiagramon.

Tipp:

  1. használd az értékelések táblából származó timestamp mezőt,
  2. használhatsz két külön cellát a megoldáshoz,
  3. gondold át hány szeletes lesz a tortadiagram.

Készíts egy függvényt, ami egy adott szakma képviselőinek óránkénti értékelésszámát adja vissza.


In [ ]:
def occupation_cnt_by_hour(ratings, occupation):
    # YOUR CODE HERE
    raise NotImplementedError()

In [ ]:
marketing = occupation_cnt_by_hour(ratings, "marketing")
assert type(marketing) == pd.Series

# 24 órás egy nap
assert len(marketing) < 25

Ábrázold tortadiagramon a marketingesek és a programozók értékelési óráit.

Először a marketingesek:


In [ ]:
# YOUR CODE HERE
raise NotImplementedError()

majd a programozók:


In [ ]:
programmer = occupation_cnt_by_hour(ratings, "programmer")

In [ ]:
# YOUR CODE HERE
raise NotImplementedError()

Q4.5. Készíts hisztogramot az értékelési kedvről! Hány user adott le N értékelést?

Segítség:

  • Az adatból hiányoznak a 20 értékelésnél kevesebbet leadó felhasználók, ami a hisztogramról könnyen leolvasható, ha jól ábrázoltad.

In [ ]:
# YOUR CODE HERE
raise NotImplementedError()

Q4.6. (Szorgalmi) Milyen volt a nemek eloszlása a romantikus filmet, illetve az akciófilmeket értékelők között? Készíts két tortadiagramot!


In [ ]:
# YOUR CODE HERE
raise NotImplementedError()

In [ ]:
# YOUR CODE HERE
raise NotImplementedError()

Q4.7. (Szorgalmi) Jóval több férfi adott le értékelést. Hogy alakulnak ezek az arányok, ha normálunk az összes értékelésre jellemző nemek arányával?


In [ ]:
# YOUR CODE HERE
raise NotImplementedError()

In [ ]:
# YOUR CODE HERE
raise NotImplementedError()

Q4.8. (**Szorgalmi) A nap melyik órájában melyik szakma értékel legtöbbször és hányszor értékelnek?

Példa válasz:

  • 0-1 óra között a mérnökök értékelnek legtöbbször, 2134-szer.
  • 1-2 óra között az oktatók (educator) értékelnek legtöbbször, 1879-szer.

Táblázatos formában elég megválaszolni.


In [ ]:
# YOUR CODE HERE
raise NotImplementedError()

5. feladat: K legközelebbi szomszéd

Ebben a feladatban a műfajok alapján fogjuk megkeresni minden filmhez a hozzá leghasonlóbb K filmet. Az eljárás neve k-nearest neighbor (KNN). A scikit-learn tartalmaz több KNN implementációt is, mi most a ball_tree-t fogjuk használni. Az osztály dokumentációja itt található: http://scikit-learn.org/stable/modules/neighbors.html

Q5.1. Nyerd ki a movies adattáblából a műfaji címkéket mátrixként!

A DataFrame values attribútumával kérhetünk le mátrixként az értékeket (oszlopnév, index stb. nélkül). Most csak a műfajokat tartalmazó oszlopokat kell megtartani. Vigyázz, az utolsó oszlop az évet tartalmazza!

A mátrix neve legyen X.


In [ ]:
# YOUR CODE HERE
raise NotImplementedError()

In [ ]:
assert type(X) == np.ndarray

Q5.2. Futtasd le a KNN-t az X mátrixon!

Ehhez bele kell nézned a NearestNeighbors dokumentációjába.

Az indexeket az indices változóban tárold.

A legközelebbi szomszédok számát a K változóban tárold. Először állítsd 4-re a K-t, később kísérletezhetsz más értékekkel is. A többi paramétert ne módosítsd, különben a tesztek nem biztos, hogy működnek.


In [ ]:
from sklearn.neighbors import NearestNeighbors
def run_knn(X, K):
    # YOUR CODE HERE
    raise NotImplementedError()

In [ ]:
K = 4
indices = run_knn(X, K)
assert type(indices) == np.ndarray

# K legközelebbi szomszédot keresünk
assert indices.shape[1] == K

Az indices változó tartalmazza az indexeket, ebből készítsünk DataFrame-et:


In [ ]:
ind = pd.DataFrame(indices)
ind.head()

Értelmezzük a táblázatot! Az index oszlop (első oszlop) azt mondja meg, hogy az X mátrix hányadik sorához tartozó szomszédok találhatók meg a sorban. A 0-3. nevű oszlopok a legközelebbi szomszédokat adják meg. Legtöbb film esetén saját maga a legközelebbi szomszédja, hiszen 0 a távolságuk, azonban nincs mindenhol így. Mit gondolsz, miért?

A táblázat indexe 0-val kezdődik, de a movies táblában a movie_id 1-től indul.

Q5.3. Állítsd át az ind DataFrame indexét úgy, hogy 1-től indexeljen! Az összes mezőt is növeld meg eggyel!

Segítség: a Pandas alapok Vektoros műveletvégzés részét érdemes megnézni.


In [ ]:
def increment_table(df):
    # YOUR CODE HERE
    raise NotImplementedError()

In [ ]:
indices = increment_table(ind)
assert indices.shape[1] == 4
assert indices.index[0] == 1
assert indices.index[-1] == len(indices)

Q5.4. Keresd meg az indexekhez tartozó filmcímeket!

Az indices táblázatban filmcímek helyett indexek vannak, ami nem túl felhasználóbarát. A movies DataFrame tartalmazza a filmeket indexekkel együtt, ezzel kell merge-ölni K alkalommal.

Pl. az első sorban megjelenő 422-es index az Aladdin and the King of Thieves film indexe. Kerüljön ez a cím az index helyére a merge után.

Segítség:

  1. az indices tábla oszlopainak nevei most nem stringek, hanem integerek,
  2. az oszlopokat át tudod nevezni a rename metódussal.
df = df.rename(columns={'regi': 'uj', 'masik regi': 'masik uj'})

A filmcímeken kívül minden oszlopot el lehet dobni.

Tipp: érdemes belenézni a kapott táblázatba, hogy reálisak-e az adatok. Pl. a Toy Story szomszédai szintén rajzfilmek lesznek.


In [ ]:
def find_neighbor_titles(movies, indices):
    # YOUR CODE HERE
    raise NotImplementedError()

In [ ]:
neighbors = find_neighbor_titles(movies, indices)
assert type(neighbors) == pd.DataFrame
assert neighbors.shape[1] == K

Q5.5. Jelenjen meg a táblázatban az a film is, aminek a szomszédjai a sorban vannak! A címeken kívül más oszlopa ne legyen a táblázatnak!

Most olyanok a soraink hogy: Jelenleg a táblázat indexe a filmek azonosítója, tehát egy szám:

nearest1 nearest 2
1 hasonló film címe 1 hasonló film címe 2

Számok helyett a filmcím legyen az index.

nearest1 nearest 2
Filmcím hasonló film címe 1 hasonló film címe 2

Sok filmnek saját maga a legközelebbi szomszédja.


In [ ]:
def recover_titles(movies, neighbors):
    # YOUR CODE HERE
    raise NotImplementedError()

In [ ]:
most_similar = recover_titles(movies, neighbors)

assert type(most_similar) == pd.DataFrame
assert "Toy Story" in most_similar.index

Q5.6. (Szorgalmi) Készíts függvényt, ami egy filmcímrészletet vesz át és megkeresi azokat a filmeket, amikben szerepel.

A függvény visszatérési értéke legyen egy DataFrame, amely a hasonló filmeket tartalmazza (akkor is, ha 1 vagy 0 hasonló film van). A most_similar táblázat szintén a függvény paramétere.


In [ ]:
def recommend_similar_movies(most_similar, title):
    # YOUR CODE HERE
    raise NotImplementedError()

In [ ]:
die_hard = recommend_similar_movies(most_similar, "Die Hard")
assert type(die_hard) == pd.DataFrame
# there are more than one Die Hard movies
assert len(die_hard) > 1

In [ ]:
asdf_movies = recommend_similar_movies(most_similar, "asdf")
assert type(asdf_movies) == pd.DataFrame

Visszajelzés

Köszönöm, hogy végigcsináltad a labort, remélem, hogy hasznosnak találtad.

Minden visszajelzés fontos és be fogom építeni következő alkalmakba. Kérlek töltsd ki ezt a rövid kérdőívet

Folytatás

Ha kedvet kaptál a témához, érdemes megnézni a kaggle.com data science oldalt, ahol megnézheted mások kódját (általában egy Jupyter Notebook formájában :)), részt vehetsz versenyeken és sokat tanulhatsz a témáról.