1 Macierze

Macierze, podobnie jak wektory, mogą zawierać elementy dowolnego, ale tego samego typu (nie można utworzyć macierzy zawierającej jednocześnie np. wartości liczbowych i tekstowych).

Podstawowa polecenie służące tworzeniu macierzy to
matrix(vec, nrow=n, ncol=p, byrow=T),
gdzie vec jest wektorem zawierającym kolejne elementy macierzy, które standardowo będą rozmieszczone kolumnami. W celu rozmieszczenia elementów wierszami należy wybrać opcję byrow=T.

1.1 Tworzenie macierzy

a=1:20
x1=matrix(a,nrow=5) #Tworzy macierz 5x4 z wektora `a` (wartości uporządkowane są kolumnami)
x1
x2=matrix(a,nrow=5,byrow=T) #Tworzy macierz 5x4 
#z wektora `a` (wartości uporządkowane są wierszami) 

1.2 Wybrane operacje na macierzach

Przetestować działanie poniższych poleceń

x3=t(x2) #Transpozycja macierzy x2
b=x1*x2 #Iloczyn macierzy element po elemencie
b=x3%*%x2 #Standardowy iloczyn macierzy
dim(b) #Podaje wymiar macierzy
b[3,2] #Wybranie elementu b32 macierzy b
b[3,2]=5 #Przypisanie wartości do elementu macierzy
b[,2] #Wybranie drugiej kolumny macierzy b
b[c(3,4),] #Wybranie wiersza 3 i 4 macierzy b
b[-2,] #Wybranie wszystkich wierszy macierzy b poza drugim wierszem
b[,-c(2,4)] #Wybranie wszystkich kolumn macierzy b poza drugą i czwartą
b[1,]>600 # Wykonanie testu na pierwszym wierszu macierzy
ind=which(b[1,]>600)
b[,ind] #Wybranie tych kolumn macierzy b , których pierwszy element
jest większy niż 600 (jako wynik wcześniejszego polecenia)
rbind(x1,x2) #Połączenie wierszowe macierzy, czyli utworzenie macierzy,
której wierszami są kolejne wiersze macierzy x1,
a następnie macierzy x2. Polecenie to pozwala na
dołączanie kolejnych wierszy do macierzy, efektem jest macierz postaci blokowej:\[ \left[ \begin{array}{c} x_1\\x_2 \end{array} \right] \]
cbind(x1,x2)

#Poziome, “kolumnowe” połączenie dwóch macierzy, czyli utworzenie macierzy, w której (licząc od lewej) najpierw są kolumny macierzy x1,
a następnie kolumny macierzy x2. W postaci blokowej:

\[\left[ \begin{array}{c c} x_1 & x_2 \end{array} \right] \]

apply(x1,2,sum) #Funkcja apply pozwala na zastosowanie funkcji działającej
na wektorach do macierzy x1 (w tym przypadku sumy)
do kolumn (drugi argument jest tutaj równy 2) lub wierszy (drugi argument byłby równy 1). W wyniku dostajemy wektor zawierający sumy elementów
poszczególnych kolumn macierzy x1
apply(x1,1,sum) #Jak wyżej, tylko tym razem otrzymujemy sumy elementów
wierszami macierzy x1
apply(x1,1,max) #Otrzymujmy wektor zawierający element maksymalny
każdego z wierszy macierzy x1

1.3 Zadania - macierze

Zadanie 1

  1. Utworzyć wektor x=(2;2;2;2;2) za pomocą funkcji rep oraz wektor y=(-1;0;1;2;3) za pomocą funkcji seq.

  2. Utworzyć macierz A o 5 wierszach i 2 kolumnach, w której pierwsza kolumna składa się z elementów wektora x a druga z elementów wektora y .

  3. Zmienić drugi wiersz macierzy A na wiersz postaci (2;4)

Zadanie 2

  1. Utworzyć macierz
    \(\mathbf{A}=\left( \begin{array}{ccc} 2&23&8\\10&6&90\\4&7&12\end{array}\right)\)
    za pomocą poleceń cbind i/lub matrix. Czy da się utworzyć tą macierz za pomocą polecenia rbind?
  2. Obliczyć średnią oraz iloczyn elementów w wierszach i kolumnach macierzy A za pomocą funkcji apply, sum oraz prod.
  3. Obliczyć sumę wszystkich liczb z dwóch pierwszych wierszy macierzy A, a następnie sumę wszystkich liczb z pierwszej i trzeciej kolumny
  4. Co zwracają polecenia t(A), det(A) i diag(A)? Wyznaczyć ślad macierzy A.
  5. Co zwracają polecenia A^2, A*A, A%*%A?
  6. Co zwracają polecenia 1/A, A^(-1), solve(A)?

2 Ramki danych

Ramki danych (data.frame) są tablicami, których kolumny mogą być niejednorodne. Przykładowo, ramka może zawierać zarówno kolumny znakowe jak i numeryczne, ale każda kolumna zawiera elementy tego samego rodzaju. Jest to najważniejsza klasa obiektów dedykowana specjalnie do przechowywania danych.

Przykładowo, w ramce danych możemy umieścić zmienne jakościowe opisywane za pomocą znaków (np. kolumnę opisującą płeć M/K osoby lub jej status społeczno-zawodowy) oraz zmienne ilościowe opisywane za pomocą liczb (np. wzrost lub wiek). Wiele narzędzi statystycznych dostępnych w R jest dedykowanych tej klasie obiektów. Ramki danych tworzone są też zazwyczaj przez R podczas importu danych.

Ramki danych mogą być utworzone poprzez zgrupowanie wektorów var1, var2,… o tej samej długości poleceniem
data.frame(nazwa1=var1, nazwa2=var2,…)

Możliwe jest też przekształcenie macierzy w ramkę danych za pomocą polecenia as.data.frame.

2.1 Tworzenie ramek danych i podstawowe operacje na ramkach

v1=sample(1:12,30,rep=T) #Losuje ze zbioru {1…12} ciąg 30 liczb z powtórzeniami
v2=sample(LETTERS[1:10],30,rep=T) #Losuje ze zbioru liter {A,B,…J} ciąg 30 liter z powtórzeniami
v3=runif(30) #30 niezależnych realizacji rozkładu jednostajnego na przedziale [0,1]
v4=rnorm(30) #30 niezależnych realizacji rozkładu normalnego o średniej 0 i wariancji 1
xx=data.frame(Age=v1,Firstname=v2,Height=v3,Weight=v4) #Tworzy ramkę danych z 4 zmiennymi i 30 osobnikami/obserwacjami
str(xx) #Wyświetla strukturę obiektu
xx$Firstname #Do zmiennych możemy się odnosić poprzez $ i nazwę
xx[,1] #Do zmiennych możemy się odnieść przez [ ], tak jak w przypadku macierzy
xx$Firstname[3] #Dostęp do trzeciego elementu wektora xx$Firstname
xx[3,2] #To samo co wyżej
summary(xx) #Podstawowe statystyki dla zmiennych
head(xx) # Wyświetla 6 pierwszy wierszów ramki danych xx
tail(xx) # Wyświetla 6 ostatnich wierszów ramki danych xx
ma=matrix(1:15,nrow=3); ma #Tworzy nową macierz
ma=as.data.frame(ma) #Zmienia macierz na ramkę danych
ma #Wyświetlenie ramki
str(ma) #Struktura ramki
names(ma)=c('VA','VB','VC','VD','VE') #Przypisanie nazw zmiennym (kolumny ramki)
row.names(ma)=c('l1','l2','l3') #Przypisanie nazw obserwacjom (wiersze ramki)

2.2 Gotowe zestawy danych

W R istnieją pewne gotowe zestawy danych, które można wykorzystać do ćwiczeń

data() Otwiera okno tekstowe z listą wszystkich tabel danych dostępnych w R
women Wyświetla tabelę z danymi “women”
? women Wyświetla opis ramki danych “women”. Dowiemy się, że wzrost jest w calach, a waga w funtach.
names(women) Nazwy zmiennych w “women”
attributes(women) Pewna charakterystyka “women”, w tym nazwy zmiennych, typ danych i nazwy obserwacji
women$height Wyświetla wartości zmiennej “height” dla ramki women
apply(women,1,sum) Funkcję “apply” można stosować do ramek danych
apply(women,2,max) Funkcję “apply” można stosować do ramek danych

2.3 Zadania - ramki danych

Zadanie 1 W analizie danych bardzo często wykorzystuje się ramki danych. Dlatego też przetestujemy poznane operacje na zbiorze danych mieszkania znajdującym się w bibliotece “Przewodnik”. Ponieważ te dane nie są dostępne w podstawowej bibliotece, wymagana jest na początku jednorazowa instalacja komendą install.packages("Przewodnik"). Następnie, wystarczy załadować bibliotekę library("Przewodnik") oraz komendą head(mieszkania) możemy wyświetlić 6 pierwszych rekordów, aby zobaczyć co składa się na rozważany zbiór danych.

Wykonaj poniższe operacje na zbiorze danych “mieszkania”:

  1. Wypisz pierwszy, trzeci i piąty wiersz ramki danych.
  2. Wypisz pierwszy, trzeci i piąty wiersz oraz od 2 do 5 kolumny ramki danych.
  3. Wpisz polecenie summary(mieszkania). Jaka jest średnia cena mieszkań? Jaka jest maksymalna powierzchnia mieszkania z naszym zbiorze danych? Ile jest mieszkań znajdujących się w wieżowcach?
  4. Aby wypisać pierwszą kolumnę, jaką jest cena mieszkań możemy użyć: mieszkania[,1] lub mieszkania$cena lub mieszkania[, "cena"]. Znajdź jaka jest największa cena za mieszkanie w rozważanym zbiorze danych, a następnie wyświetl wiersz zawierający maksymalną cenę.
  5. Używając funkcji order posortuj dane ze względu na cenę.
  6. Stwórz nową ramkę danych która zawiera tylko cenę, powierzchnię i dzielnicę mieszkań, które mają powierzchnię większą niż 20.

Zadanie 2

Poniższa tabela przedstawia liczbę studentów przyjętych w pięciu miastach z podziałem na 5 typów kierunków: nauki humanistyczne, ścisłe, medyczne, sportowe i techniczne w 2006 roku:

n. human. n. ścisłe medyczne sportowe techniczne
Bordeaux 12220 6596 7223 357 2239
Lyon 15310 6999 10921 395 3111
Paryż 112958 40244 46146 1247 7629
Rennes 8960 6170 4661 279 4013
Tuluza 12125 8233 6653 553 3178
  1. Stworzyć ramkę danych zawierających dane z powyższej tabeli. W szczególności należy uzupełnić nazwy kolumn i wierszy jak wyżej (polecenia names i row.names). Dla ułatwienia, macierz danych liczbowych można przekopiować z przykładu
macierz_miast=matrix(c(12220,15310,112958,8960,12125,6596,6999,40244,6170,8233,7223,10921,46146,4661,6653,357,395,1247,279,553,2239,3111,7629,4013,3178),ncol=5)
  1. Obliczyć całkowita liczbę studentów w danym mieście, a następnie uporządkować tabelę w porządku rosnącym względem tej liczby

  2. Obliczyć całkowita liczbę studentów danego typu kierunków, a następnie uporządkować tabelę w porządku rosnącym względem tej liczby

  3. Utworzyć nowa ramkę danych zawierającą tylko te miasta, gdzie liczba przyjętych na kierunki ścisłe jest wyższa niż liczba przyjęć na kierunki medyczne (jedno polecenie/jedna linijka)

2.4 Zadanie - metoda D’Hondta podziału mandatów w systemach wyborczych

2.4.1 Wstęp

Weźmiemy pod uwage wyniki wyborów do Sejmu RP z 15 października 2023, dane dla okręgu nr 13 (Kraków i okolice) dostępne na stronie

PKW wybory 2023

W tym okręgu do obsadzenia jest 14 mandatów, próg wyborczy 5% przekroczyło 5 komitetów wyborczych i tylko te komitety bierzemy pod uwagę:

partie=c("KO","PiS", "3Droga","Lewica", "Konfederacja")
glosy =c(232799,232430,127693,83633,58435)
l_mandatow=14

Gdyby zastosować wprost proporcjonalność zdobytych mandatów do liczby otrzymanych głosów dostalibyśmy wyniki ułamkowe:

#proporcjonalny podział głosów prowadzi do ułamkowych mandatów
razemglosy=sum(glosy)
udzial=glosy/razemglosy
ulamkowe_mandaty=l_mandatow*udzial
ulamkowe_mandaty
## [1] 4.434327 4.427298 2.432281 1.593031 1.113063

i taki wynik byłby trudny do zinterpretowania.

2.4.2 Metoda D’Hondta

Metoda ta jest algorytmem umożliwiającym niejako rozparcelowanie ułamkowych mandatów między komitety wyborcze. Opis algorytmu można znaleźć na

Wikipedia Metoda d’Hondta

W skrócie algorytm polega na zbudowaniu tabelki jak niżej, gdzie w pierwszym wierszu umieszczamy liczby głosów uzyskane przez poszczególne komitety, a następne wiersze powstają przez podzielenie (zawsze) pierwszego wiersza przez kolejne liczby naturalne. Ilość wierszy takiej tabelki odpowiada liczbie mandatów do rozdzielenia (istnieje możliwość, że ktoś zgarnie wszystkie mandaty).

Następnie z tabelki wybieramy najwyższe wyniki aż do wyczerpania liczby mandatów do obsadzenia w okręgu.

2.4.3 Zadanie właściwe - część pierwsza

Zbudować ramkę danych - tabelkę wyników z ilorazami wynikającymi z metody d’Hondta. Efekt powinien przypominać

##           KO       PiS     3Droga    Lewica Konfederacja
## 1  232799.00 232430.00 127693.000 83633.000    58435.000
## 2  116399.50 116215.00  63846.500 41816.500    29217.500
## 3   77599.67  77476.67  42564.333 27877.667    19478.333
## 4   58199.75  58107.50  31923.250 20908.250    14608.750
## 5   46559.80  46486.00  25538.600 16726.600    11687.000
## 6   38799.83  38738.33  21282.167 13938.833     9739.167
## 7   33257.00  33204.29  18241.857 11947.571     8347.857
## 8   29099.88  29053.75  15961.625 10454.125     7304.375
## 9   25866.56  25825.56  14188.111  9292.556     6492.778
## 10  23279.90  23243.00  12769.300  8363.300     5843.500
## 11  21163.55  21130.00  11608.455  7603.000     5312.273
## 12  19399.92  19369.17  10641.083  6969.417     4869.583
## 13  17907.62  17879.23   9822.538  6433.308     4495.000
## 14  16628.50  16602.14   9120.929  5973.786     4173.929

W powyższej ramce nazwy kolumn to komitety wyborcze, a nazwy wierszy to kolejne dzielniki.

Wskazówki:

  • liczba mandatów/wektor dzielników daje wektor ilorazów

  • wektory można połączyć w macierz za pomocą cbind

  • przydatne: as.data.frame, colnames, rownames

Z powyższej ramki danych należałoby wybrać 14 największych liczb (14, bo taka jest liczba mandatów w okręgu 13). Niestety, sortowanie macierzy jest nieco mało wygodne, w celu zautomatyzowania obliczeń zbudujemy inną, “lepszą” ramkę danych:

2.4.4 Zadanie właściwe - część druga

Ponieważ łatwiej posortować wektor, zbudujemy ramkę danych postaci:

##   partia   ilorazy
## 1     KO 232799.00
## 2     KO 116399.50
## 3     KO  77599.67
## 4     KO  58199.75
## 5     KO  46559.80
## 6     KO  38799.83
##          partia  ilorazy
## 65 Konfederacja 6492.778
## 66 Konfederacja 5843.500
## 67 Konfederacja 5312.273
## 68 Konfederacja 4869.583
## 69 Konfederacja 4495.000
## 70 Konfederacja 4173.929

gdzie pierwsza kolumna to kolejno sklonowane nazwy komitetów wyborczych (w liczbie równej liczbie mandatów), a druga kolumna to kolejne zestawy ilorazów.

Wskazówka: Ramkę tego typu możemy zbudować od podstaw albo korzystając z ramki z pierwszej części i funkcji stack().

Mając ramkę jak powyżej należy:

  • posortować malejąco kolumnę ilorazów ( order(), czy może sort() będzie lepszy?)

  • wybrać pierwsze 14 wyników (odpowiadających liczbie mandatów)

  • wybrać nazwy komitetów odpowiadających 14 najlepszym wynikom/ilorazom

  • zliczyć liczbę mandatów za pomocą table()

Powinniśmy dostać coś podobnego jak niżej, gdzie wyniki table() zostały jeszcze raz posortowane

## 
##           KO          PiS       3Droga Konfederacja       Lewica 
##            5            5            2            1            1
## [1] 4.434327 4.427298 2.432281 1.593031 1.113063

Mamy też porównanie z ułamkowymi mandatami, widzimy gdzie te ułamkowe części się poprzesuwały.

Można też sprawdzić, czy wyliczony przez nas podział zgadza się z oficjalnymi wynikami

2.4.5 Zadanie właściwe - część trzecia

Powtórzyć podział mandatów dla innego, wybranego okręgu i porównać z oficjalnymi wynikami.

2.4.6 Zadanie dodatkowe 1

Sprawdzić, jak wyglądałby podział mandatów, gdyby komitety zawarły koalicje, np. jakbyśmy zsumowali głosy KO+Lewica+3droga i PiS+Konfederacja.

2.4.7 Zadanie dodatkowe 2 - metoda Sainte-Laguë

Innym sposobem podziału mandatów jest wykorzystanie metody Sainte-Laguë. Różnica w stosunku do metody D’Hondta polega na tym, że dzielniki to kolejne liczby nieparzyste.

Więcej informacji na Wikipedia metoda Sainte-Laguë

Zadanie:

  • zmodyfikować kod tak, aby obliczał liczbę mandatów metodą Sainte-Laguë

  • porównać otrzymane wyniki z metody Sainte-Laguë oraz metody D’Hondta

Możliwe jest, że w niektórych okręgach liczby mandatów otrzymanych za pomocą obu metod będą różne (metoda D’Hondta bardziej sprzyja większym partiom)

2.4.8 Ciekawostka

W polskim systemie wyborczym, gdy znamy już liczbę mandatów przypadającą danemu komitetowi, bierzemy pod uwagę indywidualne wyniki kandydatów z danej listy.

Stąd też “układanki wyborcze”, tak aby znane nazwisko zdobyło nie tylko mandat dla siebie, ale znacznie poprawiło ogólny wynik listy, co daje większą liczbę mandatów.

2.5 Działania na zmiennych jakościowych (i ilościowych)

Przypomnijmy, że do ramek danych możemy umieścić zmienne jakościowe oraz zmienne ilościowe opisywane za pomocą liczb. Przyjrzymy się teraz dokładniej tym pierwszym. Zmienne jakościowe reprezentują kategorie lub grupy, które nie mają naturalnego porządku liczbowego. Przykładami mogą być płeć (mężczyzna, kobieta), kolor (czerwony, zielony, niebieski) czy odpowiedzi typu tak/nie.

sex=sample(c('M','F'),100,rep=T) Wygenerowanie 100 realizacji zmiennej “sex” o wartościach “M” i “F” (mężczyzna, kobieta)
table(sex) Wyświetla tablice liczebności dla zmiennej “sex”
barplot(sex) Graficzne przedstawienie liczebności dla zmiennej jakościowej
height=rnorm(100,mean=170,sd=10) Wygenerowanie 100 realizacji zmiennej “height” z rozkładu normalnego o średniej “mean=170” i odchyleniu standardowym “sd=10”
height[which(sex=='M')]=height[which(sex=='M')]+10 Dodanie do zmiennej “height” wartości 10, o ile zmiennej “sex” odpowiada wartość “M” - inaczej, mężczyźni statystycznie są wyżsi
tapply(height,sex,mean) Obliczenie średniej zmiennej “height” osobno dla “M” i “F”
tapply(height,sex,sd) Obliczenie odchylenia standardowego zmiennej “height” osobno dla “M” i “F”

Oczywiście, warto podkreślić, że stosowanie metod statystycznych odpowiednich dla zmiennych ilościowych do analizy zmiennych jakościowych może prowadzić do błędów lub bezsensownych wyników. Przykładem może być próba obliczenia średniej dla zmiennej jakościowej.

kolory <- c("czerwony", "zielony", "niebieski", "zielony", "czerwony")
mean(kolory)
## Warning in mean.default(kolory): argument nie jest wartością liczbową ani
## logiczną: zwracanie wartości NA
## [1] NA

2.6 Zadania - zmienne jakościowe

Zadanie 3 Kontynuując zadanie 1 i wykorzystując zbioru danych mieszkania:

  1. Oblicz średnią cenę osobno dla każdej dzielnicy, używając funkcji tapply oraz mean.

  2. Wypisz wszystkie mieszkania dla których typ budynku to wieżowiec, a następnie posortuj je ze względu na powierzchnię.

ROZWIĄZANIA 3

  1. tapply(mieszkania$cena, mieszkania$dzielnica,mean)

  2. x=mieszkania[which(mieszkania$typ.budynku=='wiezowiec'),] x[order(x$powierzchnia),]

3 Listy

Lista jest uporządkowanym zbiorem obiektów, nie koniecznie tego samego typu. Elementami list mogą być dowolne obiekty zdefiniowane w R, którym można nadać nazwy. Na przykład, można utworzyć listę, która zawiera wektor liczbowy i macierz znaków. Własność ta jest wykorzystywana w szczególności przez niektóre funkcje do zwracania złożonych wyników w formie pojedynczego obiektu. Listę tworzy się za pomocą funkcji list(nazwa1=el1, nazwa2=el2,…). Do każdego elementu listy można przejść używając jego indeksu w nawiasach podwójnych [[…]] lub jego nazwy poprzedzonej znakiem $.

li=list(num=1:5,y="color",a=TRUE) utworzenie listy z 3 obiektami z nazwami elementów listy odpowiednio num, y, a.
li wyświetlenie listy
li$num dostęp do elementu listy przez $
li$a dostęp do elementu listy przez $
li[[1]] dostęp do elementu listy przez [[…]]
li[[3]] dostęp do elementu listy przez [[…]]
a=matrix(c(6,2,0,2,6,0,0,0,36),nrow=3) utworzenie macierzy
eigen(a) obliczenie wartości własnych i wektorów macierzy
res=eigen(a) Wynik podawany jest w postaci listy
attributes(res) Pozwala na wyświetlenie nazw elementów listy
str(res) Wyświetla strukturę listy
res$values Wyświetlenie wartości własnych
res$vectors Wyświetlenie wektorów własnych
res$vectors[,1] Wyświetlenie pierwszego wektora własnego
diag(res$values) Macierz Jordana (dokładniej macierz diagonalna, której elementy ma przekątnej pochodzą z wektora zawierającego wartości własne)
res$vectors%*%diag(res$values)%*%t(res$vectors) Rozkład Jordana, macierz odwrotna = transponowana
x<-list(a=1:10,beta=exp(-3:3),logic=c(TRUE,FALSE,FALSE,TRUE)) utworzenie nowej listy
lapply(x,mean) Polecenie lapply stosuje zadaną funkcję (tutaj mean) do każdego elementu listy
lapply(x,mean) Polecenie lapply stosuje zadaną funkcję (tutaj mean) do każdego elementu listy

3.1 Zadania - Listy

Zadanie 1

Utworzyć 5-elementową listę zawierającą dowolne dwa miasta, jedno państwo, imię damskie i imię męskie, trzy zwierzęta oraz cyfrę, takie że miasto, państwo, imię, zwierze zaczynają się na tą samą literę co twoje imię a cyfra to suma cyfr twojego numeru indeksu. Nazwy elementów listy to “miasto”, “państwo”, “imię”, “zwierze” oraz “cyfra”.

Zadanie 2

  1. Utworzyć (ponownie) macierz
    \(\mathbf{A}=\left( \begin{array}{ccc} 2&23&8\\10&6&90\\4&7&12\end{array}\right)\)
    za pomocą poleceń cbind i/lub matrix.

  2. Obliczyć wartości własne i wektory własne macierzy. Czy macierz jest diagonalizowalna?

Zadanie 3 Załaduj listę, która zawiera różne typy danych. data_list <- list( numeryczny1 = c(10, 20, 30, 40, 50), numeryczny2 = c(10, 200, 34, 4, -50), kategoryczny = c("tak", "nie", "tak", "nie", "tak"), macierz = matrix(1:9, nrow=3)) Następnie, wykonaj następujące zadania:

  1. Dla wektorów numerycznych oblicz medianę.

  2. Dla zmiennych jakościowych oblicz liczbę wystąpień każdej kategorii.

  3. Dla macierzy oblicz sumę każdego wiersza i każdej kolumny.

Zadanie 4 Dla poniższej listy datalist <- list( zestaw1 = c(10, 20, 30, 40, 50), zestaw2 = c(5, 15, 25, 35, 45), zestaw3 = c(10, 10, 20, 20, 30)) używając funkcji lapply policz odchylenie standardowe oraz średnią dla każdego elementu z listy.