Power Query – tutorial cz. 2

Zapraszamy również do zapoznania się z serią Poznaj Power BI gdzie omawiane są również transformacje Power Query – serię znajdziesz tutaj.

W jednym z poprzednich postów został przedstawiony dodatek do Excela Microsoft Power Query. W tym poście temat się nie zmieni i ten wpis również będzie traktował o Microsoft Power Query, zmieni się natomiast zakres materiału. Jak już wspomniano wszystko co zostało zaprojektowane w tym dodatku, jest tłumaczone na język “M”. Dzisiaj właśnie zostaną omówione podstawy, ale nie tylko, tego języka. Zostaną zaprezentowane najważniejsze komendy i polecenia, ale także techniki, które mogą pomóc podczas pracy z Power Query i uczynić ten dodatek jeszcze bardziej przydatny.

Dlaczego uczyć się pisać coś co jest generowane z interfejsu przez program? Przede wszystkim głównym powodem jest to, iż nie wszystkie funkcjonalności zostały zaimplementowane bezpośrednio w menu graficznym Power Query. Ingerencja w kod skryptu oprócz wykorzystywania tych gotowych, ale “ukrytych” z poziomu menu, funkcji oraz transformacji pozwala również tworzyć funkcje własne, co jeszcze bardziej zwiększa zakres przydatności tego dodatku. O tym właśnie będzie dzisiejszy post. Post ma na celu przybliżyć podstawowe założenia języka M, przedstawić podstawowe komendy i polecenia oraz przykład skryptu, który realizuje konkretne zadanie. Zostanie on podzielony na dwie części:

  1. Wstęp, założenia oraz koncepcje języka M
  2. Jak to wygląda w praktyce, czyli przykład

Aby rozpocząć pisanie skryptu w Power Query należy podczas wyboru źródła danych wybrać zakładkę “From Other Sources”, a następnie “Blank Query.”

W tym momencie zostanie utworzone automatycznie nowe źródło dla PQ oraz ukaże się jego okno edycji. Na zakładce “Home” wszystkie dostępne transformacje oraz polecenia będą jednak niedostępne, ponieważ w naszym źródle tak naprawdę nie ma żadnej definicji i nie wskazuje na żadne dane/żadne miejsce z danymi. Transformacje są niedostępne, ponieważ nie ma danych na których te transformacje można by wykorzystać. Aby przejść do właściwego edytora skryptu należy wybrać zakładkę “View”, oraz następnie “Advanced Editor”.

Teraz na ekranie pojawi się edytor wraz ze szkieletem skryptu. Tak naprawdę szkielet ten prezentuje kompletne minimum, które musi posiadać każdy skrypt – po usunięciu dowolnego elementu z tego szkieletu skrypt będzie nieprawidłowy i pojawi się ostrzeżenie. Na tym etapie warto zaznaczyć, że PQ rozróżnia wielkość liter i tak na przykład zamiana słowa kluczowego “let” na “Let” w poniższym skrypcie spowoduje, że przestanie być on prawidłowy. Tyczy się to zarówno poleceń jak i nazw zmiennych.

Skrypt Power Query zawsze składa się z kombinacji dwóch elementów poprzedzonych dwoma słowami kluczowymi: “let” oraz “in”. “let” poprzedza wszystkie “kroki” w skrypcie, czyli np. “Source = “””. Krok jest odzwierciedleniem pewnego stanu w skrypcie do którego można się odwoływać, ale również reprezentuje daną wartość/wartości lub funkcję. Póki co może to wydawać się nieintuicyjne, ale za wkrótce na pewno się rozjaśni. Dla przykładu zmiana powyższego skryptu na:

 

… spowoduje, że “zmienna” czy też słowo kluczowe o nazwie “Source” będzie od tej pory równe 1. Użycie natomiast tego słowa jako źródła za poleceniem “in” sprawi, że PQ przejdzie do danego kroku, czyli zwróci wartość 1.

Po zakończeniu edycji źródła w arkuszu pojawi się wartość “1”.

W przypadku zmiany kodu na:

Skrypt również zwróci wartość 1, ponieważ w źródle (“in”) jest odwołanie właśnie do “Source”. Pokazuje to właśnie schemat działania PQ, który nie zatrzymuje się na ostatnim zdefiniowanym poleceniu (kroku) tylko wskazanym po słowie kluczowym “in”. W tym miejscu należy również dodać, że poszczególne kroki (zwane dalej również poleceniami) należy oddzielać przecinkiem. Warto również nadmienić, że w jednym skrypcie można łączyć wiele skryptów, to znaczy w jednym skrypcie można zapisać wiele sekwencji typu “let … in …”. Jak widać pisanie skryptów w PQ może trochę przypominać między innymi pisanie skryptów w języku R czy dla programu MatLab.

Podstawy zostały przedstawione już wyżej, ale uściślając, to przede wszystkim podczas pisania skryptów w języku M należy szczególnie pamiętać o tym, że:

  • W skrypcie musi znajdować się przynajmniej jedna para słów kluczowych “let” i “in” oraz przynajmniej jedno polecenie oraz odwołanie do niego
  • PQ rozróżnia wielkość liter
  • Każde polecenie musi być oddzielone za pomocą przecinka
  • Nie można używać dwa razy polecenia (kroku) o tej samej nazwie

Na tym etapie warto również dodać, że aby dodać komentarz do skryptu można użyć następujących kombinacji:

Kolejną istotną kwestią w każdym języku są typy danych oraz ich obsługa przez środowisko. Power Query automatycznie identyfikuje typ danych – analizuje oraz sam sprawdza jaki typ danych zwróci poszczególne polecenie (krok) i dlatego nie ma potrzeby jawnego definiowania typu. Oczywiście można zmienić typ danego atrybutu lub rzutować do konkretnego typu, ale nie jest to zupełnie konieczne przy deklaracji. Oznacza to zatem, że wszystkie z poniższych poleceń będą poprawne:

Co więcej następujące definicje również będą poprawne :

Oprócz tradycyjnych typów danych język M wyróżnia również pewne złożone struktury: listy, rekordy oraz tablice. Lista to uporządkowany zbiór wartości (wartości mogą być dowolnego typu). Definiuje się ją w klamrach.

Rekord to zbiór pól, przy czym pole należy rozumieć jako parę nazwa oraz wartość, czyli na przykład nazwa: rok, wartość: 2014.

Tablica to zbiór rekordów lub pól zorganizowanych w kolumny. Tabelę tworzy się za pomocą funkcji #table(). Na przykład następujące polecenie utworzy tablicę o wymiarach 2×2

To polecenie zwróci następujące dane:


Jak już zaznaczono do konkretnych kroków można oprócz wartości definiować funkcje.

Funkcje tworzy się za pomocą operatora “=>”, a poprzedzając go w nawiasie definiuje się nazwy parametrów wejściowych funkcji. PQ sam analizuje oraz sam obsługuje typ danych jaki funkcja będzie zwracać (mimo wszystko można rzutować do konkretnego typu). Jak widać powyższa funkcja przyjmuje jeden parametr, czyli zmienną o nazwie x, a treść funkcji wskazuje, że jest ona odpowiedzialna za podwojenie wartości przekazywanego do niej parametru.

Oczywiście w PQ istnieje mnóstwo gotowych, predefiniowanych funkcji i transformacji, które można w skrypcie wykorzystać. Najpopularniejsze zostaną zaprezentowane poniżej.

  1. Jak to wygląda w praktyce, czyli nauka przykład

Poniżej zostanie zaprezentowany przykładowy skrypt języka M. Niecierpliwych i dociekliwych zapraszam do całego opis wszystkich funkcji oraz kompleksowych omówień, które można znaleźć w literaturze, którą podam na końcu.

Poniżej zostaną zaprezentowane wybrane funkcje oraz zostanie zaprezentowany sposób rozwiązania jednego z problemów jakie można zrealizować z wykorzystaniem Power Querry i języka M.

Celem przykładu jest przeprowadzenie podstawowej analizy tekstu. Dla tego celu pobrano jedną z dostępnych książek w formacie txt ze strony projektu Gutenberg (http://www.gutenberg.org), a konkretnie tekst książki “Alicja w krainie czarów” oraz zapisano go na dysku twardym komputera. Założeniem jest zbadanie podstawowych parametrów analizy tekstu, takich jak:

– liczba słów

– liczba unikalnych słów

– usunięcie słów niekluczowych (stopwords)

– sprawdzenie najczęściej występujących słów

– sprawdzenie najczęściej występujących słów z pominięciem słów niekluczowych

– sprawdzenie najczęściej występujących n-gramów w tekście (2-gramów)

W tym celu zostaną przygotowany trzy skrypty, które zbadają częstotliwość pojedynczych słów dla całego tekstu, częstotliwość słów z pominięciem słów niekluczowych oraz częstotliwość 2-gramów. Reszta z zagadek rozwiąże się tak naprawdę sama i będzie możliwa do odczytania po wykonaniu tych trzech skryptów. Wszystkie trzy skrypty są do siebie bardzo podobne i różnią się kilkoma funkcjami, tutaj zostanie zaprezentowany jeden z nich.

Na samym początku została przygotowana funkcja, która pozwala usunąć wszystkie znaki inne aniżeli litery. Patrząc pod kątem analizy tekstu znaki typu przecinek, kropka lub wykrzyknik są zwykle bezwartościowe i są usuwane z badanego tekstu. W tym celu należy skorzystać z funkcji Text.Remove(), która z podanego ciągu znaków usuwa wskazane znaki. W tym przypadku ciąg tekstowy jest przekazywany jako parametr funkcji, a listę znaków specjalnych zdefiniowano bezpośrednio w funkcji.

Następnie, w pierwszej linii kodu, należy wczytać dane z pliku tekstowego. Tak naprawdę cała operacja musi zostać podzielona na kilka etapów: wczytanie pliku jako pliku binarnego: File.Contents(), konwersja z pliku binarnego na zestaw linii tekstu Lines.FromBinary(), konwersja tych linii do tablicy Table.FromColumns().

Przed przejściem do jakichkolwiek dalszych operacji trzeba, jak wyżej wspomniano, usunąć wszystkie znaki specjalne. W tym celu skorzystano z wcześniej przygotowanej funkcji, a także wykorzystano jeden z tricków w PQ. Mianowicie, w języku M, nie ma czegoś takiego jak pętle. Wszystkie operacje definiowane są tak naprawdę na podstawie kolumn, a nie rekordów, ale można skorzystać z metody, która jest wykorzystywana podczas dodawania nowej kolumny, który zawiera słowo kluczowe “each” i dla każdego rekordu nowej kolumny wykonuje jakąś operację dla każdego rekordu właśnie. Do osiągnięcia powyższego celu można również użyć kilku innych funkcji, które działają na podobnej zasadzie. Tak więc należy wykorzystać funkcję Table.AddColumn(), skorzystać z wcześniej wczytanych danych “sourceData”, wskazać nazwę dla nowej kolumny “DataWithoutStopWords” oraz wskazać, aby dla każdego rekordu w nowej kolumnie wartość z kolumny Column1 została przetworzona za pomocą przygotowanej funkcji.

Jako, że zdefiniowana funkcja zwraca wartości nie jako ciąg znaków, ale listę wartości (mimo, że jest to lista jednoelementowa) należy przed dalszymi operacjami zmienić typ kolumny, a właściwie to “rozwinąć” listę dla każdego rekordu używając funkcji Table.ExpandListColumn() oraz wskazując nazwę źródła danych “RemoveSpecialCharacters” oraz nazwę kolumny w tym źródle, która zawiera listę “DataWithoutStopWords”

Teraz, pierwotna kolumna z tekstem wraz ze znakami specjalnymi jest niepotrzebna i można ją usunąć. Służy do tego polecenie Table.RemoveColumns(), gdzie wskazuje się źródło danych oraz kolumnę lub listę kolumn, które są niepotrzebne.

W celu lepszego przygotowania danych do analizy należy jeszcze zmienić wszystkie litery w tekście na małe (lub wielkie), ponieważ wyraz “kot” i “KOT” w późniejszej analizie reprezentowany by był przez dwa różne słowa podczas gdy jest to to naprawdę jedno słowo. W tym celu wystarczy użyć funkcji Table.TransformColumns(), w której należy wskazać źródło danych “removedSourceColumn”, wskazać kolumny “{}” – czyli wszystkie, oraz wybrać rodzaj transformacji: Text.Lower odpowiada właśnie za zamianę liter z wielkich na małe.

Po tych operacjach w kolejnych rekordach tabeli znajdują się kolejne linie tekstu z pliku, które są przygotowane do analizy. Do analizy wykorzystywane jednak powinny być pojedyncze słowa, a nie całe linie tekstu. Nadszedł więc czas, aby podzielić poszczególne linie na słowa. Aby podział ten był w pełni dynamiczny można użyć pewnej sztuczki. Funkcja Table.SplitColumn(), która pozwala podzielić ciąg znaków względem znaku oczekuje podania liczby kolumn na które mają one zostać podzielone i dzieli dany tekst na tablicę w której kolejna kolumna to następny wyraz – jeżeli linia ma mniej wyrazów niż poprzednia to komórka posiada wartość null, aby kod był w pełni dynamiczny trzeba zatem najpierw określić ile w danym tekście maksymalną liczbę słów w linii. Oto przykładowy sposób rozwiązania tego problemu. Najpierw należy sprawdzić ile w danej linii jest słów, aby to zrobić można za pomocą funkcji Text.PositionOf() pobrać listę wszystkich pozycji znaku spacji – w tym celu użyto przełącznika Occurrence.All. Pozycje te nie są jednak ważne w tym przypadku, ale przy pomocy funkcji List.Count można policzyć ile było wystąpień w danym tekście – ile razy pojawiła się spacja (jedna pozycja w liście odpowiada jednej spacji, a jedna spacja jednemu słowu, licząc ilość elementów listy można określić liczbę słów). Operacje taką można osadzić w funkcji dołączenia nowej kolumny co spowoduje, że zostanie ona powtórzona dla każdego rekordu. Wynikiem będzie oryginalna linia tekstu oraz liczba wyrazów.

Dla pewności można zmienić typ nowododanej kolumny na numeryczny za pomocą funkcji Table.
TransformColumnTypes.

W tym przypadku ważna jest jednak tylko największa wartość spośród wszystkich tak więc należy posortować dane według ilości wyrazów, malejąco za pomocą funkcji Table.Sort()

Następnie należy usunąć kolumnę z tekstem i zostawić tylko tę z liczbą słów. W tym celu trzeb użyć funkcji Table.RemoveColumns()

Teraz wystarczy wybrać pierwszy z góry rekord, który będzie odpowiadał największej liczbie słów w linii. Do tego służy funkcja Table.FirstN(), która przyjmuje wyłącznie źródło danych oraz liczbę topowych rekordów do pobrania.

Poprzednia operacja zwróciła jednak wynik jako listę wartości, więc, aby otrzymać finalną odpowiedź należy wybrać za pomocą funkcji List.First() pierwszy element listy.

Posiadając wiedzę o maksymalnej ilości słów w liniach można użyć do podziału funkcji Splitter.SplitTextByDelimiter() wskazując znak podziału jako znak spacji oraz liczbę kolumn jako wcześniej wyliczoną wartość.

W wyniku takiego podziału zwrócona została jednak tablica, która zawiera kilkadziesiąt kolumn z pojedynczymi słowami. Aby spłaszczyć te dane do jednej kolumny można użyć funkcji Table.Unpivot(). Wymaga ona jednak podania nazwy kolumn, które mają zostać poddane procesowi spłaszczenia. Kontynuując skrypt z myślą o uniwersalności należy listę tych kolumn dostarczyć za pomocą samego skryptu. Nie jest to jednak problemem w przypadku języka M i można w tym celu wykorzystać funkcję Table.ColumnNames(), która dla podanego źródła danych listę wszystkich kolumn.

Poniżej przykład wyniku po podziale tekstu na słowa.


Teraz wreszcie można spłaszczyć dane do jednej kolumny tak, aby każde słowo było w osobnym wierszu. W tym celu należy użyć funkcji Table.Unpivot(), wskazać odpowiednie źródło “splitedColumnByDelimiter”, listę kolumn “addedColumnNames” oraz nazwy kolumn, które mają zostać poddane powstać po przekształceniu. Warto zaznaczyć, że wymagane jest podanie dwóch kolumn z czego pierwsza będzie prezentować nazwę kolumny z której pochodzi dana wartość, a druga właśnie wartość.

Nie jest ona jednak zupełnie potrzebna, więc można ją po prostu usunąć funkcją Table.RemoveColumns()

Na końcu dla pewności można wybrać z tablicy tylko niepuste wartości. Można to zrobić za pomocą polecenia Table.SelectRows wskazując, że do tablicy ma zostać zakwalifikowana tylko i wyłącznie wartość różna od ciągu pustego “”.

Ostatnim krokiem jest policzenie konkretnych wystąpień słów w tabeli. Można w tym celu wykorzystać funkcję grupującą Table.Group(), która sama zliczy w ilu wierszach pojawiła się dana wartość.

Przed zwróceniem danych do warstwy prezentacyjnej można jeszcze zmienić nazwy poszczególnych kolumn, aby były jednoznacznie identyfikowalne.

Cały kod prezentuje się następująco:

Pozostałe skrypty są niemalże bliźniacze. Poniżej zostaną zaprezentowane tylko różnice. W przypadku skryptu w którym lista słów miała zostać pomniejszona o słowa, które są tzw stopwords dodano trzy elementy. Pierwszym z nich jest wczytanie do tabeli listy takich słów z jednej ze stron interenetowych.

Drugim natomiast jest funkcja functionCheckIfStopword, która wyraz przekazywany do funkcji wykorzystuje w funkcji języka M List.Contains(), która porównuje go z listą wyrazów typu stopwords i zwraca warunek logiczny. Funkcja ta jest wykorzystywana tak jak poprzednio podczas operacji dodawania nowej kolumny.

Na koniec wybierane są tylko te słowa, których funkcja nie znalazła na liście ze strony i dla których zwróciła wartość false.

W przypadku ostatniego skryptu została dopisana metoda tworzenia n-gramów,a konretnie 2-gramów. Sztuczka polega na wstawieniu za pomocą funkcji Table.AddIndexColumn() nowej kolumny, która będzie zawierać numer wiersza – indeks,

Następnie za pomocą funkcji Table.Range() można pobrać z tej samej tabeli, która jest bieżącym zestawem danych wyraz z rekordu bieżącego oraz wyraz następny. Następnie należy wynik tej funkcji przekształcić na listę za pomocą funkcji Table.ToList() oraz połączyć poszczególne elementy listy – czyli poszczególne wyrazy – przecinkiem za pomocą funkcji Text.Combine().

Z analizy tekstu “Alicji w krainie czarów” otrzymano wyniki takie jak na grafice u góry postu, prawda, że Power Query jest fajny? Zainteresowanym polecam przede wszystkim materiały dostępne na stronie: http://office.microsoft.com/en-us/excel-help/learn-about-power-query-formulas-HA104003958.aspx

Zdjęcie do raportu pochodzi ze strony: http://goo.gl/NswovX

Slawomir Drzymala
Follow me on

Slawomir Drzymala

Still playing with data and .NET technologies
Slawomir Drzymala
Follow me on

Leave a Comment

Your email address will not be published. Required fields are marked *