1 Biblioteka plotly

1.1 Opis biblioteki plotly

Biblioteka plotly umożliwia tworzenie interaktywnych wykresów. Dostępna jest w językach/środowiskach programowania:

  • JavaScript - podstawowa, na niej oparte są pozostałe wersje

  • Python - najbardziej rozwinięta

  • R/RStudio

  • Julia

  • Matlab

  • F#

Wymaga jednorazowego zainstalowania w standardowy sposób. Wymaga też każdorazowego ładowania biblioteki library(plotly) (wystarczy 1 raz w skrypcie/R Markdown), często używana jest w połączeniu z pakietem dplyr.

Porównanie kodów w różnych językach

Poniższe przykłady mają za zadanie porównanie składni w wymienionych wyżej językach. Każdy z kodów ma za zadanie narysować te same trzy punkty jako wykres punktowy.

  • JavaScript
#wygenerowany przez ChatGPT
<div id="plot"></div>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<script>
  var trace = {
    x: [0, 2, 4],
    y: [0, 1, -3],
    mode: 'markers',
    type: 'scatter'
  };

  Plotly.newPlot('plot', [trace]);
</script>
  • Python
#wygenerowany przez ChatGPT
import plotly.express as px

x = [0, 2, 4]
y = [0, 1, -3]

fig = px.scatter(x=x, y=y)
fig.show()
  • R
library(plotly)

x = c(0, 2, 4)
y = c(0, 1, -3)

plot_ly(x = ~x, y = ~y, type = "scatter")
  • Julia
#wygenerowany przez ChatGPT
using PlotlyJS

x = [0, 2, 4]
y = [0, 1, -3]

scatter(x = x, y = y, mode = "markers")

1.2 Poruszanie się po wykresie

Zaczniemy od wykonania przykładowego rysunku (kopiuj/wklej poniższy kod). Przy rysowaniu drugiej funkcji korzystamy z operatora %>% z pakietu dplyr.

Wybrane elementy, na które warto zwrócić uwagę (wypróbować)

  • wykres z zamierzenia jest łamaną, najechanie na wykresie na dany węzeł pokazuje dokładne wartości w danym punkcie

  • kliknięcie na nazwę w legendzie pozwala na włączenie/wyłączenie serii danych na wykresie

  • po najechaniu kursorem na wykres pojawia się panel z opcjami

  • można powiększyć wybrany fragment wykresu, w tym “rysując” prostokąt na fragmencie wykresu do powiększenia

  • szybko zresetować wykres możemy poprzez podwójne kliknięcie na wykresie

  • wybranie “Pan” - ikonka ze strzałkami w pojawiającym się panelu pozwala na przesuwanie wykresu

  • wypróbować “Compare data on hover” - taka podwójna strzałka

library(plotly)
library(dplyr)

x = seq(-pi, 2*pi, length.out = 30)


wart_sin = sin(x)
wart_cos = cos(x)

# wykres
plot_ly(x=~x, y=~wart_sin, type="scatter", mode="lines", name="sinus") %>%
  add_lines(x = ~x, y = wart_cos, name = "cosinus") 

1.2.1 Zadanie - pierwszy wykres plotly

Zmodyfikować powyższy wykres w następujący sposób:

  • zwiększyć liczbę punktów podziału siatki do 100. Czy w powiększeniu dalej widać łamaną?

  • dodać do powyższego wykresu dwie wybrane funkcje (np. \(\sin(2x)\), \(\mathrm{arctg}(x)\), ale można też inne).

  • zadbać o odpowiednie opisy na legendzie

Uwaga: tutaj korzystamy z przykładu i ogólnego doświadczenia z R, szczegóły omówimy w dalszej części laboratorium.

Przykładowy wykres - trochę dodatkowych zmian

1.3 plotly i ggplot czyli niczego nowego nie musisz się uczyć. Ale czy aby na pewno?

Polecenie ggplotly(rysunek_ggplot2) pozwala zamienić rysunek wykonany w ggplot2 na jego interaktywną wersję pakietu plotly. W efekcie można tworzyć interaktywne wykresy opierając się na samym ggplot2, który powinien być wszystkim dobrze znany.

Przykład - utworzymy wykres punktowy ggplot2:

library(ggplot2)
rys_gg=ggplot(iris)+
  geom_point(aes(x=Sepal.Length, y=Sepal.Width, color=Species))
rys_gg

i przekształcimy na interaktywny wykres plotly:

ggplotly(rys_gg)

1.3.1 Zadanie - ggplotly na własnym rysunku

Spróbować przerobić własny wykres ggplot2 na rysunek plotly poprzez funkcję ggplotly. Można wykorzystać np. paproć Barnsleya, inny rysunek z przeszłości lub stworzyć nowy (nie musi być szczególnie skomplikowany).

1.3.2 Zadanie - boxplot ggplota do plotly’a

Powyższe rozwiązanie wydaje się idealne, ale nie zawsze działa. Spróbować utworzyć wykres ggplot typu boxplot dla Sepal.Length (ramka iris wbudowana), a następnie przekształcić ją przez ggplotly.

1.3.3 Utrata formatowania przy konwersji ggplo2 na plotly

Rozważmy przykładowy rysunek, jaki był do wykonania w jednym z ćwiczeń na 1 stopniu. Zadanie polegało na dodaniu wybranych szczegółów do rysunku, w tym odpowiedniego sformatowania klucza legendy. Wykres powinien wyglądać jak na rysunku niżej

Niestety, po transformacji do plotly niektóre szczegóły ulegają zatarciu, np. pozycjonowanie legendy czy pomocnicze linie siatki:

Wniosek: jakieś elementy plotly wypada znać.

2 plotly - wprowadzenie

2.1 Podstawowy wykres liniowy i punktowy

Zaczniemy od przygotowania danych do wykresów - można je umieścić w ramce danych, ale nie jest to niezbędne.

Chcemy narysować wykres punktowy/liniowy wybranych danych. Zaczynamy od plot_ly, gdzie podajemy naszą ramkę jako parametr data=df (samo data jest pierwszym argumentem, można czasem pominąć nazwę). Tutaj możemy też zdefiniować pierwsza linię, jaka pojawi się na wykresie. Podajemy typ wykresu type="scatter oraz mode="markers+lines" aby narysować wykres liniowy z zaznaczonymi punktami.

Następnie (poprzez operator %>% z pakietu dplyr lub |> ze standardowego R) dodajemy wykres punktowy \(\cos (x)\) za pomocą add_markers. Wykres liniowy \(\cos (2x)\) możemy dodać albo poprzez add_lines albo poprzez ogólne add_trace z podaniem typu wykresu. Mamy tutaj sporą dowolność i elastyczność w doborze elementów wykresu.

#Przygotowanie danych 
x = seq(-pi, 4*pi, length.out = 50)
y1 = sin(x)
y2 = cos(x)
y3 = cos(2*x)
y4 = atan(x)
df=data.frame(x=x, wart_sin=y1, wart_cos=y2, wart_cos2x = y3, wart_arctg=y4)

plot_ly(data=df, x=~x,
        y=~wart_sin,
        type="scatter",
        mode="markers+lines") %>% 
  add_markers(x=~x,
              y=~wart_cos,) %>% 
  add_trace(x=~x,
            y=~wart_cos2x,
            type="scatter",
            mode="lines")

2.2 Nazwy, kolory - dodawanie kolejnych elementów

Dodamy teraz nazwy, własne kolory oraz inne elementy do poszczególnych serii danych. Ze względu na dziedziczenie przez wszystkie trace parametrów podanych w plot_ly, wykres \(\sin (x)\) narysujemy osobno, poza głównym poleceniem plot_ly.

Serie danych możemy dodawać poprzez ogólne polecenie add_trace() i wewnątrz niego określić typ wykresu lub poprzez wyspecjalizowane polecenia, np. add_markers do dodania wykresu punktowego.

#dane jak wcześniej

plot_ly(data=df) %>% 
add_trace(x=~x,
        y=~wart_sin,
        type="scatter", #typ wykresu
        mode="markers+lines", #punkty zaznaczone "markerami" i połączone liniami
        name="sin(x)",
        line=list(color="red"),
        marker=list(color="blue", size=10, line=list(color="purple", width=3))) %>% 
  add_markers(x=~x, #sam wykres punktowy
              y=~wart_cos,
              name="cos(x)",
              opacity=0.5,
              marker=list(color="darkorange", symbol="triangle-up", size=15,line=list(color="black", width=2))
              ) %>% 
  add_trace(x=~x,
            y=~wart_cos2x,
            type="scatter", #typ wykresu
            mode="lines", #tym razem tylko linie
            name="cos(2x)",
            line=list(color="cornflowerblue", width=6, dash="dash"))

2.3 Pozostałe elementy wykresu

Poprzez dodanie layout() do wykresu możemy zmodyfikować jego elementy, typu kolory, nagłówki, ich wygląd, ogólne zachowanie (np. skumulowany wykres słupkowy).

Narysujemy prosty wykres ramka-wąsy box i dodamy do niego oraz zmodyfikujemy wygląd elementów tekstowych

plot_ly(data=iris) %>% 
  add_trace(x=~Sepal.Length, type="box", color=~Species) %>% 
  layout(
    title=list(text="Tytuł wykresu",
               font=list(
                  family = "Courier New",
                  size = 40,
                  color = "green")
               ), #tytuł wykresu
    legend=list(bgcolor="red", #tło legendy
                title=list(
                  text="Jestem legendą",
                  font=list(size=15)
                  )
                ), #legenda
    yaxis = list(showticklabels = FALSE, #etykiety te same co w legendzie są zbędne
                 title=list(text='Gatunek',
                            font=list(size=18, family='Courier', color='crimson')
                            )
                 ), #dostosowanie osi Y
    xaxis = list(
      title=list(text='Długość płatka',
                 font=list(size=18,
                           family='Comic Sans MS',
                           color='crimson')
                 )
                ), # oś X
  margin = list(t = 100) # bo tytuł się nie mieścił, zwiększamy margines
  )

A teraz skupimy się na kolorach, ten sam wykres, ale modyfikujemy inne elementy:

plot_ly(data=iris) %>% 
  add_trace(x=~Sepal.Length, type="box", color=~Species) %>% 
  layout(
    paper_bgcolor="#ffff32", #kolor tła "papieru", czyli całego obszaru
    plot_bgcolor="lightblue", #kolor tła wykresu
    legend=list(bgcolor="red"), #kolor tła legendy
    yaxis = list(
           gridcolor = '#19955a'), #kolor linii siatki poziomych
    xaxis = list(
           gridcolor = '#000000')  #kolor linii siatki pionowych
  )

2.4 Więcej opcji, więcej przykładów

Opcji formatowania wykresu jest całkiem dużo, podobnie jak w ggplot2, nie będziemy ich tutaj szczegółowo omawiać.

Po dalsze przykłady odsyłam do dobrze przygotowanej dokumentacji i galerii.

3 plotly - zadania

3.1 Zadanie - pobranie danych energii

Zaczniemy od pobrania i wczytania pliku danych tabela_energia_2020_2025.xlsx ze strony przedmiotu. Plik zawiera zestawienie miesięczne produkcji energii elektrycznej w Polsce z podziałem na źródła energii. Dane podane są w [GWh]. Źródło danych: Agencja Rynku Energii.

Zapoznać się ze strukturą danych w pliku (polecenia summary oraz str). Prawdopodobnie kolumna “Miesiac” zostanie wczytana jako char, aby przetransformować ją na zmienną typu Date można użyć następującego polecenia

energia$Miesiac=as.Date(paste(energia$Miesiac, "/01", sep="")) #moja nazwa ramki to energia

Pozostałe dane powinny być wczytane jako zmienne liczbowe (sprawdzić, czy tak jest rzeczywiście).

3.2 Zadanie - wykres ramka-wąsy (box)

Narysować wspólny wykres ramka-wąsy dla wszystkich źródeł energii. Podpisać osie, nadać wykresowi stosowny tytuł.

Przykładowy wykres - nie musi być idealnym odwzorowaniem

3.3 Zadanie - wykres liniowy (scatter, lines)

Narysować wykres liniowy zmian produkcji energii w czasie dla każdego źródła energii. Zadbać o dodatkowe elementy (podpisy itp.). Legendę umieścić pod wykresem. W przypadku osi X ustawić format daty na “miesiąc-rok”.

Przykładowy wykres - nie musi być idealnym odwzorowaniem

3.4 Zadanie - wykres słupkowy skumulowany (bar, barmode="stack")

Narysować wykres słupkowy skumulowany miesięcznych danych produkcji energii. Dodać szczegóły jak wcześniej.

Przykładowy wykres - nie musi być idealnym odwzorowaniem

3.5 Zadanie - porównanie udziału

3.5.1 Przygotowanie danych

Utworzyć nową ramkę danych o kolumnach jak ramka energia, a w wierszach należny obliczyć/podać sumę produkcji energii w całym roku (dla pełnych lat 2020-2024). O ile nie ma błędu, u mnie wychodzą wartości jak niżej. Wyświetlić sformatowaną za pomocą pakietu gt tabelkę.

Roczna produkcja energii
dane w [GWh]
rok wegiel_kamienny wegiel_brunatny gaz_ziemny biomasa_i_biogaz el.wodne fotowoltaika el.wiatrowe pozostale_paliwa
2020 71674.6 38501.5 16048.7 8167.0 2590.3 2025.0 14986.8 3338.8
2021 84075.2 46005.6 15236.2 7714.3 2770.6 3844.1 16404.2 3298.9
2022 73546.1 39636.5 14765.4 7809.9 2601.8 5491.4 15856.5 3262.9
2023 64634.6 36175.9 15698.0 8028.6 2923.1 9185.3 23939.6 3112.9
2024 59245.9 36067.4 19539.6 7869.9 2808.2 15107.6 24566.6 3130.5

3.5.2 Udział procentowy

Otrzymaną tabelkę z danymi rocznymi przedstawić w postaci udziału procentowego danego źródła energii w danym roku. Tabelka powinna wyglądać jak niżej. Dane można zaokrąglić do np. drugiego miejsca po przecinku.

  • Tabelkę ponownie przedstawić jako obiekt gt (minimum ozdobników, tj. tytuł wykresu jest niezbędny).

  • Zilustrować te dane na wykresie skumulowanym słupkowym w orientacji poziomej.

  • Może się przydać polecenie prop.table z odpowiednią opcją odpowiedzialną za działanie na wierszach (standardowo wyznaczy proporcje dla całej tablicy).

Roczna produkcja energii
procentowy udział w danym roku
rok wegiel_kamienny wegiel_brunatny gaz_ziemny biomasa_i_biogaz el.wodne fotowoltaika el.wiatrowe pozostale_paliwa
2020 45.56 24.47 10.20 5.19 1.65 1.29 9.53 2.12
2021 46.88 25.65 8.50 4.30 1.54 2.14 9.15 1.84
2022 45.13 24.32 9.06 4.79 1.60 3.37 9.73 2.00
2023 39.48 22.10 9.59 4.90 1.79 5.61 14.62 1.90
2024 35.20 21.43 11.61 4.68 1.67 8.97 14.59 1.86
Przykładowy wykres - nie musi być idealnym odwzorowaniem

3.6 Zadanie - wykresy i wnioski

3.6.1 Przykład

Zaczniemy od porównania danych całkowitej produkcji energii oraz samej fotowoltaiki w kolejnych latach na wykresach:

foto=roczne[,c("rok", "fotowoltaika")]
sumy=data.frame(rok=roczne$rok, suma=rowSums(roczne[,2:9]))

plot_ly() %>% 
  add_trace(data=foto, x=~rok, y=~fotowoltaika, type="bar", name="Dane historyczne - fotowoltaika") %>% 
    layout(
    title="Historyczne dane produkcji energii elektrycznej z fotowoltaiki",
    yaxis = list(title = "[GWh]"),
    xaxis = list(title = "Rok"),
    margin = list(t = 100, b=100),
      legend = list(
      orientation = "h",
      x = 0.5,             
      y = -0.3,           
      xanchor = "center",
      yanchor = "top"
    )
    )
plot_ly() %>% 
  add_trace(data=sumy, x=~rok, y=~suma, type="bar", name="Całkowita produkcja") %>% 
    layout(
    title="Historyczne dane o całkowietej produkcji energii elektrycznej",
    yaxis = list(title = "[GWh]"),
    xaxis = list(title = "Rok"),
    margin = list(t = 100, b=100),
      legend = list(
      orientation = "h",
      x = 0.5,             
      y = -0.3,           
      xanchor = "center",
      yanchor = "top"
    )
    )

Wniosek - mocno nieuprawniony - jest taki, że produkcja z fotowoltaiki rośnie wykładniczo, a całkowita produkcja energii pozostaje względnie stabilna - może nieco liniowy wzrost jest widoczny.

Spróbujemy dopasować krzywą wykładniczą postaci \(y=a \cdot e^{b \cdot x}\) do danych fotowoltaiki oraz prostą \(y=ax+b\) dla danych całkowitej produkcji, a następnie wykonać prognozę dla lat 2025-2028 na podstawie dopasowania.

#dopasowanie krzywej wykładniczej za pomocą funkcji nls, fotowoltaika

fit= nls(fotowoltaika ~ a * exp(b * (rok-2020)), data = foto, start = list(a = 2025, b = 0.1))

#współczynniki dopasowania
a=coef(fit)["a"]
b=coef(fit)["b"]


#prognoza
y_2025= a * exp(b * (2025 - 2020))
y_2026= a * exp(b * (2026 - 2020))
y_2027= a * exp(b * (2027 - 2020))
y_2028= a * exp(b * (2028 - 2020))

#prognoza w dataframe
foto_nowe=data.frame(rok=c(2025,2026,2027, 2028), fotowoltaika=c(y_2025, y_2026, y_2027, y_2028))


# dopasowanie linii prostej dla całkowitej energii
fit2= nls(suma ~ a * (rok-2020)+b, data = sumy, start = list(a = 2025, b = 1))

#współczynniki dopasowania
a2=coef(fit2)["a"]
b2=coef(fit2)["b"]


#prognoza liniowa dla całej energii
y2_2025= a2 * (2025 - 2020)+b2
y2_2026= a2 * (2026 - 2020)+b2
y2_2027= a2 * (2027 - 2020)+b2
y2_2028= a2 * (2028 - 2020)+b2

#prognoza w dataframe
sumy_nowe=data.frame(rok=c(2025,2026,2027, 2028), suma=c(y2_2025, y2_2026, y2_2027, y2_2028))


#wykres - porównanie
plot_ly() %>% 
  add_trace(data=foto, x=~rok, y=~fotowoltaika, type="bar", name="Fotowoltaika - dane historyczne") %>% 
  add_trace(data=foto_nowe, x=~rok, y=~fotowoltaika, type="bar", name="Fotowoltaika - prognoza") %>%
add_trace(data=sumy, x=~rok, y=~suma, type="bar", name="Całkowita produkcja - dane historyczne") %>% 
add_trace(data=sumy_nowe, x=~rok, y=~suma, type="bar", name="Całkowita produkcja - prognoza") %>% 
  layout(
    title="Do 2028 roku ponad połowa produkcji energii <br> może pochodzić z fotowoltaiki",
    yaxis = list(title = "[GWh]"),
    xaxis = list(title = "Rok"),
    margin = list(t = 100, b=100),
      legend = list(
      orientation = "h",   # poziomo
      x = 0.5,             # środek legendy
      y = -0.3,            # pozycja poniżej wykresu
      xanchor = "center",
      yanchor = "top"
    )
  )

Powyższe rozumowanie jest pełne błędów, w tym nierealnym założeniu o dalszym wzroście wykładniczym produkcji energii z fotowoltaiki czy nie uwzględnieniu dynamicznego wzrostu tej produkcji w całkowitej produkcji.

Model wykładniczy sprawdza się dla wielu zjawisk w ograniczonym czasie. Lepszym modelem bywa model logistyczny, gdzie w pewnym momencie następuje załamanie wzrostu wykładniczego (punkt przegięcia) i wypłaszczenia krzywej.

3.6.2 Zadanie właściwe

Przedstawić wybrane dane dot. produkcji energii w wybrany sposób oraz zinterpretować je w sposób rzetelny lub zmanipulowany. Opisać wnioski, sposób ewentualnego zmanipulowania, dobór danych dowolny.

Można zajrzeć na stronę z przykładami wykresów w plotly R.

Kilka przykładów manipulacji danymi dostępne na stronie pod linkiem.

4 Animacje w plotly

4.1 Materiały

Na stronie dokumentacji znajdują się przykłady tworzenia prostych animacji w plotly. Na tej podstawie wykonać poniższe ćwiczenia.

4.2 Zadanie - błądzenie losowe na płaszczyźnie

Zilustrować za pomocą animacji plotly realizację błądzenia losowego na płaszczyźnie. Sama animacja niewiele różni się od pierwszego przykładu, z tym że przygotowanie danych będzie nieco ciekawsze:

  • zakładamy z góry liczbę ruchów, np. 50

  • poruszamy się tylko po współrzędnych całkowitych

  • startujemy z losowego punktu kraty \(\mathbb{Z} \times \mathbb{Z}\), ja ograniczyłem się do \(\{-5, -4,..., 4,5\} \times \{-5, -4,..., 4,5\}\)

  • w każdym kroku współrzędna punktu zmienia się o \(\pm 1\). U mnie zmieniają się obie jednocześnie, ale można też wylosować jedną do zmiany.

  • powyższe wytyczne można zmodyfikować

  • do losowania ze zbioru dyskretnego można użyć polecenia sample, warto zwrócić uwagę na parametr replace=TRUE dla losowania z powtórzeniami.

Przykładowe rozwiązanie - nie musi być idealnym odwzorowaniem

4.3 Zadanie - błądzenie losowe na płaszczyźnie - więcej punktów

Dołożyć do animacji kolejne punkty (razem co najmniej 3 punkty).

Przykładowe rozwiązanie dla 10 punktów

4.4 Zadanie (testowe) - błądzenie losowe punktu ze śladem

Wykonać zadanie błądzenia losowego jednego punktu, ale tak żeby trasa punktu pozostała zaznaczona jako linia ciągła.

Jeszcze nie mam rozwiązania, ale nie powinno być to bardzo trudne. Jako zadanie testowe jest nieobowiązkowe

4.5 Zadanie - animowane słupki lub inne dane ze zbioru danych z produkcją energii

Dane z wczytanego pliku tabela_energia_2020_2025.xlsx indeksowane są datą, zatem dobrze nadają się do ilustrowania zmian wybranych wielkości w czasie.

Utworzyć wybraną animację dla wybranych danych z tego zbioru lub odtworzyć zaprezentowany niżej przykład.

Do stworzenia poniższego przykładu zamieniłem postać wyjściową ramki na format długi (polecenie pivot_longer z pakietu tidyr) oraz datę na zmienną znakową (w formacie daty nie chciało działać prawidłowo)

Przykładowa animacja w wykresie słupkowym