Lista zmieści wszystko
Wektor ma jedną twardą zasadę: każdy element musi być tego samego typu. Lista takiej zasady nie ma. Każdy element listy może mieć inny typ, inną długość, a nawet być kolejną listą. To uniwersalny kontener w R:
length() zwraca 4, czyli cztery elementy, choć jeden z nich (scores) sam zawiera trzy liczby. Lista liczy swoje przegródki, a nie to, co w nich jest. str() (od structure) to najszybszy sposób, żeby zobaczyć, co naprawdę jest w liście: jedna linia na element z nazwą, typem i podglądem. Niech str() wejdzie ci w odruch: gdy funkcja zwraca coś, czego nie znasz, wywołaj na tym str().
Nazwy są opcjonalne (list(1, "a", TRUE) jest poprawne), ale prawie zawsze warto je mieć, bo nazwana lista sama się dokumentuje.
Trzy drogi do środka: [, [[ i $
Tu każdy choć raz się myli, więc ustalmy to dokładnie. Są trzy operatory dostępu i wykonują dwie różne prace:
[zwraca mniejszą listę: kontener tego samego rodzaju z tym, co wybrano.[[zwraca sam element: faktyczną wartość z przegródki.$to skrót od[[z dosłowną nazwą:person$agetoperson[["age"]].
Różnica jest niewidoczna przy pobieżnym wypisaniu i bardzo widoczna, gdy tylko spróbujesz coś policzyć:
person["scores"] ma klasę "list": to lista długości 1 z wektorem wyników w środku. person[["scores"]] ma klasę "numeric": to sam wektor, gotowy do mean(). Wywołaj mean() na wersji z pojedynczym nawiasem, a dostaniesz błąd, że argument nie jest liczbowy. Taki komunikat prawie zawsze oznacza, że użyto [ zamiast [[.
Metafora, która zostaje w głowie: lista to pociąg. [ daje krótszy pociąg, ale nadal pociąg. [[ otwiera wagon i podaje ci ładunek.
Kiedy więc [ jest właściwym narzędziem? Gdy chcesz kilka elementów i chcesz je zachować w opakowaniu:
Nie da się wyjąć dwóch elementów "samych w sobie" naraz, bo dwie wartości potrzebują kontenera. Dlatego wybór wielu elementów zawsze używa pojedynczego nawiasu i zawsze daje listę.
Jeszcze jedna różnica: $ stosuje częściowe dopasowanie (person$sc znajdzie scores, jeśli nie ma niejednoznaczności). W konsoli to wygodne, w skryptach to ryzyko. W kodzie, który ma działać długo, wybieraj [[ z pełną nazwą: przyjmuje też nazwę zapisaną w zmiennej, czego $ nie potrafi.
Dodawanie, zmiana, usuwanie
Listy rosną i kurczą się przez zwykłe przypisanie, bez specjalnej metody do dopisywania:
- Przypisanie do nazwy, która jeszcze nie istnieje, dodaje element.
- Przypisanie do istniejącej nazwy zastępuje go.
- Przypisanie na pozycję
length(x) + 1dopisuje element bez nazwy. - Przypisanie
NULLcałkowicie usuwa element i lista się skraca. (To jedyne miejsce, w którymNULLdziała jak polecenie usunięcia; jeśli naprawdę chcesz przechować "nic" w przegródce, użyjx["k"] <- list(NULL).)
Żeby skleić dwie listy jedną za drugą, c() działa na listach tak samo jak na wektorach: c(list_a, list_b) zwraca jedną dłuższą listę.
Zagnieżdżone listy
Element listy może być kolejną listą, więc listy zagnieżdżają się na dowolną głębokość. Dzięki temu są naturalnym kształtem w R dla danych strukturalnych, takich jak sparsowany JSON albo pogrupowane wyniki:
Schodzenie w głąb to po prostu łączenie operatorów dostępu: każde $ albo [[ schodzi o jeden poziom niżej. Gdy zagnieżdżona struktura robi się myląca, str(company) pokazuje całe drzewo naraz, a str(company, max.level = 1) tylko górną warstwę.
Spłaszczanie przez unlist()
unlist() zwija listę, niezależnie od głębokości zagnieżdżenia, do jednego wektora:
Zwróć uwagę na dwie rzeczy. Po pierwsze, unlist() sam buduje nazwy (math1, math2, art), więc nadal widać, skąd pochodzi każda wartość. Po drugie, i to jest pułapka: wektor przechowuje tylko jeden typ, więc spłaszczenie mieszanej listy konwertuje wszystko do najbardziej elastycznego z obecnych typów. Liczba 1 wróciła jako napis "1". Jeśli po spłaszczeniu twoje liczby zamieniają się w tekst, lista nie była tak jednolita, jak się wydawało.
Dlaczego listy mają znaczenie
Listy mogą wyglądać na temat dla początkujących, z którego się wyrasta. Jest odwrotnie: podtrzymują cały język:
- Funkcje, które zwracają kilka rzeczy, zwracają listę. Funkcje w R zwracają jeden obiekt, a lista pozwala temu jednemu obiektowi przenosić tu dopasowaną wartość, tam tabelę współczynników. Gdy uruchamiasz regresję, zwracany obiekt modelu to (duża, z nadaną klasą) lista, co udowodni
str(). - Ramka danych to lista kolumn: wektorów równej długości z dodatkowym zachowaniem na wierzchu. Wszystko, co tu omówiliśmy (
$,[[, przypisanieNULLdo usunięcia elementu), stosuje się dosłownie do kolumn ramki danych. - Rodzina apply, czyli
lapply()i pokrewne, przyjmuje listę, stosuje funkcję do każdego elementu i zwraca listę. Listy na wejściu, listy na wyjściu to standardowy kształt powtarzanych obliczeń w R.
Co warto zapamiętać
- Listy mieszczą wszystko: różne typy, nierówne długości, inne listy, całe ramki danych.
[zwraca mniejszą listę;[[zwraca sam element;$to[[z dosłowną nazwą. Chcesz liczyć? Potrzebujesz[[albo$.- Dodawaj przez przypisanie do nowej nazwy, usuwaj przez przypisanie
NULL, łącz przezc(). unlist()spłaszcza do wektora i po drodze konwertuje mieszane typy, więc sprawdzaj klasę wyniku.- Ramki danych to listy kolumn, więc ta wiedza przenosi się bezpośrednio.
Dalej: macierze, czyli jednotypowy odpowiednik listy z wierszami i kolumnami.
Najczęściej zadawane pytania
Czym różni się [ od [[ w R?
Pojedyncze nawiasy [ zawsze zwracają listę z wybranymi elementami, czyli mniejszy kontener tego samego rodzaju. Podwójne nawiasy [[ sięgają do środka i zwracają sam element. Jeśli x$scores przechowuje wektor liczbowy, to x["scores"] jest listą długości 1, a x[["scores"]] jest wektorem liczbowym. Używaj [[ (albo $), gdy chcesz faktycznie pracować z wartością.
Jak dodać element do listy w R?
Przypisz wartość do nazwy, która jeszcze nie istnieje: x$email <- "rosa@example.com" albo x[["email"]] <- .... Lista rośnie automatycznie. Żeby dopisać element bez nazwy, przypisz go na kolejną pozycję: x[[length(x) + 1]] <- value. Żeby usunąć element, przypisz mu NULL: x$email <- NULL.
Jak zamienić listę na wektor w R?
unlist(x) spłaszcza listę (również zagnieżdżoną) do jednego wektora. Wektor przechowuje tylko jeden typ, więc wszystko jest konwertowane do najbardziej elastycznego z obecnych typów: lista liczb i napisów staje się wektorem samych napisów. Po spłaszczeniu sprawdź class() wyniku.