Pracując ze sparkiem bardzo często spotykamy mechanizmy, które budzą naszą konsternację z powodu bardzo dużego podobieństwa. Czy każde z tych mechanizmów ma inne działanie? Jeśli tak, to jakie są praktyczne różnice? Postanowiłem zanurkować nieco w kod i sprawdzić najbardziej popularne funkcje. Zapraszam!
Aha… to oczywiście artykuł w serii “Zrozumieć Sparka”. Poprzednio omawiałem joiny – zainteresowanych zapraszam tutaj.
Porządkujemy – czyli orderBy vs sort
Pierwszy mechanizm, który bierzemy na tapet to sortowanie. Spark daje nam przynajmniej dwie funkcje, które spełniają to zadanie: orderBy() oraz sort(). Czym się różnią?
Tutaj sprawa jest banalnie prosta – orderBy() to po prostu alias dla sort(). Mówi nam o tym poniższy fragment kodu:
/** * Returns a new Dataset sorted by the given expressions. * This is an alias of the `sort` function. * * @group typedrel * @since 2.0.0 */ @scala.annotation.varargs def orderBy(sortExprs: Column*): Dataset[T] = sort(sortExprs : _*)
Mamy dwie funkcje orderBy() – obie wywołują sort().
Zmieniamy nazwy kolumn: withColumnRenamed vs alias
Załóżmy, że mamy dataframe i nie pasuje nam nazwa jednej z kolumn. W takiej sytuacji mamy do dyspozycji dwie – cztery metody: withColumnRenamed(), alias(), as() oraz name(). Zajmijmy się najpierw różnicami między withColumnRenamed oraz alias.
- Zwracany typ:
- alias() to funkcja zwracająca typ Column,
- withColumnRenamed() zwraca Dataset[Row] (czyli Dataframe).
- Co dzieje się w środku:
- alias() jest jedynie… aliasem dla funkcji name(). Nie robi nic innego. O tym co robi name() rozdział niżej.
- withColumnRenamed() ma logikę, która zmienia schemat. Warto dodać, że sama zmiana nazwy kolumny dzieje się poprzez wykorzystanie funkcji as(). To – zdradzę przedwcześnie – jest alias dla aliasu. A alias jest aliasem dla name(). WOW! No nic… poniżej podrzucam jak zbudowana jest funkcja withColumnRenamed():
def withColumnRenamed(existingName: String, newName: String): DataFrame = { val resolver = sparkSession.sessionState.analyzer.resolver val output = queryExecution.analyzed.output val shouldRename = output.exists(f => resolver(f.name, existingName)) if (shouldRename) { val columns = output.map { col => if (resolver(col.name, existingName)) { Column(col).as(newName) } else { Column(col) } } select(columns : _*) } else { toDF() } }
- Kiedy stosować:
- alias() stosujemy wtedy, gdy możemy odwołać się do konkretnej kolumny – klasycznym przykładem jest zastosowanie funkcji select(). Tak jak poniżej:
peopleDF.select(col("name").alias("firstName"))
- withColumnRenamed() wywołujemy na Dataframe. W efekcie dostajemy nowy Dataframe ze zmienioną nazwą kolumny. Wygląda to tak jak poniżej:
peopleDF.withColumnRenamed("name", "firstName")
- alias() stosujemy wtedy, gdy możemy odwołać się do konkretnej kolumny – klasycznym przykładem jest zastosowanie funkcji select(). Tak jak poniżej:
Zmienianie nazw ciąg dalszy – as vs alias vs name
Skoro już znamy różnice między withColumnRenamed oraz alias, warto przejść do kolejnych meandrów sparkowego labiryntu. Tym razem poszukajmy czym różnią się od siebie trzy funkcje: as(), alias() oraz name().
Otóż, tym razem sprawa jest prostsza. Albo i bardziej zaskakująca, sam(/a) już musisz to rozstrzygnąć. Otóż – nie ma różnic. A konkretniej as() jest aliasem dla name(), tak samo jak alias() jest aliasem dla name().
Funkcja name() natomiast jest dość prostym mechanizmem. Jeśli obecna kolumna ma jakieś metadane powiązane z nią, zostaną propagowane na nową kolumnę. W implementacji użyty jest Alias – warto jednak pamiętać, że nie chodzi tu o funkcję alias(), a o case classę Alias. Implementacja funkcji name() poniżej:
def name(alias: String): Column = withExpr { expr match { case ne: NamedExpression => Alias(expr, alias)(explicitMetadata = Some(ne.metadata)) case other => Alias(other, alias)() } }
Usuwamy duplikaty w Sparku – distinct vs dropDuplicates
Prędzej czy później przychodzi taki moment, że w efekcie różnych transformacji dostajemy identyczne obiekty w naszych dataframach. Jest to szczególnie uciążliwe, gdy musimy pracować na unikatowych danych. Poza tym jednak najzwyczajniej w świecie niepotrzebnie zajmują one miejsce. Aby tego uniknąć, usuwamy duplikaty. Tylko jak to zrobić w Sparku? Istnieją dwa sposoby – distinct() oraz dropDuplicates().
Distinct jest najprostszą formą usuwania duplikatów. Tutaj sprawa jest prosta – na dataframe wywołujemy funkcję distinct(), po czym Spark wyszukuje rekordy, które w całości się pokrywają i zostawia tylko jeden z nich. Tak więc w tym przypadku wszystkie kolumny muszą się pokrywać, aby rekord został uznany za duplikat.
Implementacja:
sampleDF.distinct()
Sprawa ma się nieco inaczej, jeśli weźmiemy na warsztat funkcję dropDuplicates(). W tym przypadku możemy wybrać które kolumny bierzemy pod uwagę. W takiej sytuacji “duplikatem” może być np. osoba o tym samym imieniu i nazwisku, ale z innym PESELem.
Implementacja z przykładowymi kolumnami:
sampleDF.dropDuplicates("name", "age")
Co ważne – po takim wywołaniu, zwrócony dataframe oczywiście będzie zawierał wszystkie kolumny, nie tylko wyszczególnione w “dropDuplicates()”.
Tak więc, podsumowując – różnica polega na tym, że distinct() bierze wszystkie kolumny, zaś dropDuplicates() pozwala nam wybrać o które kolumny chodzi.
Explode vs ExplodeOuter
Niekiedy posługując się dataframe, stykamy się z kolumną, która zawiera listę – np. listę imion, liczb itd. Czasami w takiej sytuacji potrzebujemy, żeby te dane znalazły się w osobnych wierszach. Aby to zrobić, używamy funkcji explode – a właściwie jednej z dwóch “eksplodujących” funkcji.
Różnica jest bardzo prosta: explode() pominie nulle, natomiast explode_outer() rozbuduje naszą strukturę także o nulle.
Tak więc, jeśli chcemy rozbić nasze dane pomijając pry tym wartości typu null – wybierzemy explode().
Podsumowanie
Mam nadzieję, że zestawienie powyższych metod rozwieje różne wątpliwości, które mogą niekiedy przychodzić. Spark to złożona, duża technologia. Jeśli chcesz zgłębić ją bardziej – przekonaj swojego szefa, żeby zapisał Ciebie i Twoich kolegów/koleżanki na szkolenie ze Sparka. Omawiamy całą budowę od podstaw, pracujemy na ciekawych danych, a wszystko robimy w miłej, sympatycznej atmosferze;-) – Zajrzyj tutaj!
A jeśli chcesz pozostać z nami w kontakcie – zapisz się na newsletter. Koniecznie, zrób to!