sobota, 17 stycznia 2015

Duża macierz podobieństwa i wizualizacja gatunków muzycznych w Polsce.

Problem

Poprzednio skorzystaliśmy z dwóch systemów bazo-danowych do przechowywania danych pobranych "na żywca" (Web Scraping) oraz przez WebApi  Przechowywanie dużych zbiorów danych na dysku pozbawia Nas problemu przeładowania pamięci podręcznej, przez co możemy sobie swobodnie wybierać podpróby, tworzyć agregaty, modyfikować dane itd.. Problemem pozostaje jednak poruszanie się w nowym środowisku zastępując część operacji, które wykonalibyśmy w R, zapytaniami w języku SQL i MongoDB (JSON). Ponieważ ani SQL ani MongoDB nie są tak rozbudowane jeżeli chodzi o manipulacją danymi, zaprezentuję R jako dobrego pomocnikia w tworzeniu zapytań. R może znacznie ułatwić workflow z zewnętrznymi bazami danych, uruchamiając całe zestawy wygenerowanych zapytań zamiast pisania kwerend jedna po drugiej.
Drugim problemem jest dalsze postępowanie z wyselekcjonowanymi danymi, które pomimo tego, że są tylko skrawkiem całej bazy nadal pozostają nadal dużym kłopotem. W pierwszej kolejności na przeszkodzie stoi przekształcanie do zbioru finalnego, co z wykorzystaniem popularnych pakietów takich jak np. dplyr czy tidyr jest nie możliwe. Często rozwiązaniem w takich przypadkach jest zejście poziom niżej i inwencja samego programisty. Na szczęście nie jest aż tak źle, bowiem społeczność R-owa dostarcza pakiety, które trochę ułatwiają pracę, cobyśmy nie musieli grzebać w C++ (przynajmniej dzisiaj nie musimy). Dzisiejszym problemem jest analiza podobieństwa pomiędzy wszystkimi tagami z last.fm przypisanymi do wszystkich wykonawców notowanych na liście Olis w całym okresie trwania. Nie komplikując sprawy czyszczeniem, liczebność zbioru tagów to 20573. Oznacza to, że taka macierz podobieństwa miałaby 423mln elementów, jak to zrobić w R? Jak potem wyciągnąć wnioski z takiej macierzy?
Rozwiązanie

W przypadku pracy z bazami danych zwykle ułatwiamy sobie sprawę generowaniem kwerend, zamiast pisania wszystkich zapytań po kolei w skrypcie R. Na przykład gdybyśmy chcieli usunąć cudzysłowy z kilku wybranych kolumn, zawsze lepiej zaprogramować to w taki sposób jak poniżej. Dla czytelności skryptu zawsze dodajemy komentarz wydzielający każdą sekcję, przez co takie miejsca jak poniższe będą ławo identyfikowalne. Nie opłaca się taki trick gdy mamy do uruchomienia dwie kwerendy ale przy ośmiu, dziesięciu, stu+ różnica jest wyraźna. Przy okazji z bazy olis wyodrębniamy listę wszystkich wykonawców i kończymy pracę z bazą SQLite.
Podobnie można postąpić w przypadku MongoDB, w Moim przykładzie trzeba było wygenerować 1375 zapytań do bazy last.fm, ponieważ właśnie tylu unikalnych wykonawców było notowanych na Olisie.

Możemy już połączyć się z bazą MongoDB, tak samo jak opisywałem to przed tygodniem. Tutaj muszę się podzielić tym miłym doświadczeniem - nie całe dwie sekundy zajęło 1375 zapytań z użyciem przeciętnego PC (4GB RAM i 3,1GHz). To ważne, że pakiet rmongodb nie generuje dużego opóźnienia w komunikacji pomiędzy R, a MongoDB. Polecam! Wyniku poniższych komend otrzymujemy listę z zagnieżdżonymi danymi z last.fm dla wybranych wykonawców. Następnie przeczyszczono listy z elementów powodujących błędy w następujących etapach, takie jak NULL'e itp. Dla lepszej czytelności procesu pomijam dodatkowe etapy, w których zachodziło czyszczenie i porządkowanie danych. Chciałbym zwrócić również uwagę, że część kodów, które prezentuję nie są odtwarzane 1:1 bez uprzedniego oczyszczenia danych i zintegrowania obydwu baz. Pierwszą część zatem można traktować jako podgląd pod proces generowania danych, którymi dzielę się z Państwem na końcu. Domyślnie używam pakietu "magrittr", który daje możliwość stosowania tzw. pipe operator %>% przewijającego się w części prezentowanych kodów.

W następnym etapie musimy wyodrębnić dane ze skomplikowanej listy będącej odpowiednikiem struktury w MongoDB. Powyżej część danych dla pierwszego artysty na liście. Każdy z artystów określony jest tagami (słowami kluczowymi) z przypisanymi współczynnikami trafności oznaczonymi jako "count". Pomysł na analizę bierze się z następujących założeń:
  • Użytkownicy tagują danego artysty wg. jakiejś wiedzy na temat gatunków muzycznych. Każdy artysta oceniany jest przez wielu użytkowników. Trafność danego taga określona jest przez współczynnik count, przyjmujący max(count)=100.  Wykonawca charakteryzuje się następującymi wymyślonymi danymi
  • Name count
    Rock 100%
    Guitar 95%
    Soft Rock 90%
    (...) (...)
    Tennessee Teenager Soft Rock Guitar 5%

    Ponieważ gatunki dotyczą jednego artysty można uznać, że gatunki ze sobą sąsiadują (w obrębie jednego artysty). Nie powinniśmy jednak wszystkich gatunków traktować jednakowo, ponieważ część tagów ma niską wiarygodność ze względu na mały współczynnik. Dlatego macierz sąsiedztwa skonstruujemy jako wynik mnożenia wag. Np. sąsiedztwo(Guitar i Soft Rock)=0.95*0.9=0.855. Obliczono w ten sposób sąsiedztwo dla wszystkich kombinacji, tworząc ważoną macierz sąsiedztwa, która de facto staje się macierzą podobieństwa. Poniższa funkcja wyodrębnia odpowiednie dane z bazy last.fm, dokonuje kilka przekształceń i generuje macierz podobieństwa/sąsiedztwa dla określonego wykonawcy. Ważne żeby w procesach tego typu nie korzystać z data.frame, spowolni Wam to proces kilkudziesięciokrotnie. Po wywołaniu funkcji dla każdej z list, otrzymujemy zbiór macierzy podobieństw dla każdego artysty.
  • Analizując wykonawców na last.fm, zauważymy niektóre tagi występują częściej podczas gdy inne rzadziej. Niektóre kombinacje zdarzają się jednokrotnie, a jeszcze inne nie występują w ogóle. Dlatego zbudujemy macierz podobieństwa, która będzie sumą macierzy podobieństwa poszczególnych wykonawców. Dzięki temu takie kombinacje jak "hip-hop" i "rap" będzie miało wysokie podobieństwo dzięki częstemu współwystępowaniu, uniezależniając podobieństwo od przypadkowych, jednostkowych obserwacji. Ponadto przedmiotem analizy są artyści z listy Olis, dlatego też każda z macierzy podobieństwa przeważona jest czasem przebywania na liście. Dzięki temu końcowa macierz podobieństwa będzie dotyczyć listy Olis (polskiego rynku muzycznego), a nie wybranych artystów ogółem. Macierz podobieństwa będzie macierzą kwadratową, w której większość elementów będzie równych zeru. To jest kluczowy moment dzisiejszego artykułu. Możemy spróbować wygenerować pustą macierz rozmiarów 20.000x20.000 i czekać jak szybko pojawi się komunikat "Cannot allocate vector of size ...". Odpowiedzią na ten problem jest pakiet "Matrix".

  • Sumę macierzy podobieństwa obliczymy jedna po drugiej, dodając wartości z małych macierzy do odpowiadających im elementów dużej macierzy. Proces zajmuje trochę czasu (ok 3-4 min.). W wyniku czego otrzymujemy to czego oczekiwaliśmy - macierz podobieństwa tagów notowań listy Olis.

Sama macierz podobieństwa nie jest produktem finalnym, zwykle poddawana jest dalszej analizie i wizualizacji. W tym celu użyłem pakietu 'igraph', który często służy mi do wizualizacji podobieństwa. W ten sposób można zaprezentować np. macierz korelacji. Funkcja 'igraph.adjacency' akceptuje obiekty utworzone z pomocą pakietu "Matrix", co znacznie upraszcza dzisiejszą zabawę. Poniższy kod generuje wykres zaprezentowany na końcu wpisu. Wielkość punktów przy tagach reprezentuje wagę danego taga, czyli popularność danego "gatunku" w Polsce. Szerokość wiązania jest odpowiednikiem podobieństwa między poszczególnymi tagami. Wnioski pozostawiam zainteresowanym, powodzenia.

Brak komentarzy:

Prześlij komentarz