7 Souvislé příklady

Gratuluju, dostali jste se až do poslední kapitoly, kde vám ukážu pár souvislých příkladů propojujících látku celé knihy.

7.1 Poznámky z porady

Na schůzce s klientem jsem si do Keepu v telefonu poznamenal úkoly, které mám udělat. Ke každému úkolu jsem si napsal odhad náročnosti v hodinách a požadovaný termín. Vypadá to nějak takhle:

- Rychlá analýza UX a designu: 8 hodin, T 1.3.
- Klíčovka čeština: 16 hodin, T 15. 3.
- Klíčovka angličtina: 8 hodin, T 15. 3.
- Návrh obsahové strategie: 8 hodin, T 20.3.
- Příprava workshopu pro tým: 2 hodiny, T 1.4.

Teď chci ocenit jednotlivé úkoly hodinovou sazbou a sečíst celkový čas i cenu. S tak malým seznamem to nejrychleji udělám ručně, ale kdyby byl delší, můžu si práci usnadnit spreadsheetem nebo R. Vybral jsem si R :-)

7.1.1 Které balíčky budu potřebovat

Kromě tidyverse ještě lubridate, což je chytrý balíček na práci s časovými údaji – datum, čas, doba, časový interval apod.

library(tidyverse)
library(lubridate)

7.1.2 Načtení textu

Text si zkopíruju z Keepu a načtu ho do proměnné input funkcí read_lines, která vrátí znakový vektor jednotlivých řádků. Parametr skip říká, že se má přeskočit první (prázdný) řádek, parametr locale určuje kódování znaků a je potřeba jen ve Windows. Na Macu nebo Linuxu ho tam nedávejte.

input <- read_lines("
  - Rychlá analýza UX a designu: 8 hodin, T 1.3.
  - Klíčovka čeština: 16 hodin, T 15. 3.
  - Klíčovka angličtina: 8 hodin, T 15. 3.
  - Návrh obsahové strategie: 8 hodin, T 20.3.
  - Příprava workshopu pro tým: 2 hodiny, T 1.4.
", skip = 1, locale = locale(encoding = "Windows-1250"))

Zkontroluju si, jestli se řádky podařilo správně načíst:

input
## [1] "  - Rychlá analýza UX a designu: 8 hodin, T 1.3."
## [2] "  - Klíčovka čeština: 16 hodin, T 15. 3."        
## [3] "  - Klíčovka angličtina: 8 hodin, T 15. 3."      
## [4] "  - Návrh obsahové strategie: 8 hodin, T 20.3."  
## [5] "  - Příprava workshopu pro tým: 2 hodiny, T 1.4."

Podařilo, takže mohu vytvořit data frame.

7.1.3 Převod na data

Vytvořím data frame a v něm řádky rozdělím na samostatné proměnné (sloupce). Pak přidám hodinovou sazbu a dopočítám cenu. Nejprve ukážu celý postup a pak ho vysvětlím.

df <- 
  data.frame(input) |>
  extract(
    col = 1,
    into = c("task", "hours", "day", "month"),
    regex = " - ([^:]+): ([0-9]+).+T ([0-9]+).+([0-9]+)",
    convert = TRUE
  ) |> 
  mutate(
    deadline = make_date(year(today()), month, day),
    rate = 2500,
    price = hours * rate
  ) |> 
  select(task, hours, price, deadline)

Povedlo se?

df

7.1.3.1 Podrobný výklad postupu

Pokud je vám až sem všechno jasné, tohle přeskočte. Pro ostatní popíšu postup vytvoření data framu podrobněji.

Nejprve jsem vytvořil data frame s jedním sloupcem obsahujícím řádky původního vstupního textu. Posloužila mi funkce data.frame ze základního R.

data.frame(input)

Pomocí funkce extract z balíčku tidyr (součást tidyverse) jsem jednotlivé řádky rozdělil na víc sloupců. Použil jsem k tomu tyto parametry:

  • col: říká, který sloupec bude vstupní; zde jen pořadovým číslem, ale jde i názvem či jinak.
  • into: znakovým vektorem udává názvy nových sloupců, které funce vytvoří.
  • regex: regulární výraz, který určuje obsah nových sloupců pomocí skupin v kulatých závorkách.
  • convert: hodnota TRUE říká, že se mají číselné hodnoty převést z řetězce na čísla.
data.frame(input) |>
  extract(
    col = 1,
    into = c("task", "hours", "day", "month"),
    regex = " - ([^:]+): ([0-9]+).+T ([0-9]+).+([0-9]+)",
    convert = TRUE
  )

Pomocí funkce mutate z balíčku dplyr (součást tidyverse) jsem přidal tři další vypočítané sloupce deadline, rateprice. Pro deadline jsem použil funkci make_date z baličku lubridate, která jako parametry chce rok (ten jsem vzal jako rok z dnešního data: year(today())), měsíc a den. Ostatní je asi jasné.

data.frame(input) |>
  extract(
    col = 1,
    into = c("task", "hours", "day", "month"),
    regex = " - ([^:]+): ([0-9]+).+T ([0-9]+).+([0-9]+)",
    convert = TRUE
  ) |> 
  mutate(
    deadline = make_date(year(today()), month, day),
    rate = 2500,
    price = hours * rate
  )

Funkcí select už jen vyberu sloupce, které chci skutečně zařadit do výsledného data framu.

data.frame(input) |>
  extract(
    col = 1,
    into = c("task", "hours", "day", "month"),
    regex = " - ([^:]+): ([0-9]+).+T ([0-9]+).+([0-9]+)",
    convert = TRUE
  ) |> 
  mutate(
    deadline = make_date(year(today()), month, day),
    rate = 2500,
    price = hours * rate
  ) |> 
  select(task, hours, price, deadline)

A nakonec celý výsledek na prvním řádku uložím do proměnné df.

df <- 
  data.frame(input) |>
  extract(
    col = 1,
    into = c("task", "hours", "day", "month"),
    regex = " - ([^:]+): ([0-9]+).+T ([0-9]+).+([0-9]+)",
    convert = TRUE
  ) |> 
  mutate(
    deadline = make_date(year(today()), month, day),
    rate = 2500,
    price = hours * rate
  ) |> 
  select(task, hours, price, deadline)

7.1.4 Součet hodin a ceny

Zbývá sečíst cenu a převést hodiny na člověkodny (sloupec md).

df |> 
  summarise(
    md = sum(hours) / 8,
    price = sum(price)
  )

7.2 Roční rozpočet freelancera

Pracujete na volné noze, klientům fakturujete vždy na konci měsíce odpracované hodiny jednotnou hodinovou sazbou, máte nějaké opakující se výdaje, z nichž některé jsou v cizí měně, a jste plátci DPH. Chcete si sestavit roční finanční plán.

7.2.1 Které balíčky budou potřeba

Kromě již známých tidyverselubridate to bude priceR, který umí načíst měnové kurzy.

library(tidyverse)
library(lubridate)
library(priceR)

7.2.2 Kurzovní lístek

Protože budu mít ve výdajích zahraniční měny, stáhnu si nejprve aktuální kurzy CZK funkcí exchange_rate_latest z balíčku priceR. Výsledný kurzový lístek si uložím do objektu exchange_rates a již známou funkcí glimpse se podívám, jak vypadá.

exchange_rates <- exchange_rate_latest("CZK")
## Daily CZK exchange rate as at end of day 2022-03-30 GMT
exchange_rates |> glimpse()
## Rows: 169
## Columns: 2
## $ currency                 <fct> AED, AFN, ALL, AMD, ANG, AOA, ARS, AUD, AWG, ~
## $ one_czk_is_equivalent_to <dbl> 0.166794, 3.983811, 4.998511, 22.096493, 0.08~

7.2.3 Plán výdajů

Všechny výdaje si sepíšu do přehledné tabulky s těmito sloupci:

  • name – název položky,
  • cost – částka bez DPH,
  • cur – měna,
  • vatr – sazba DPH,
  • period – za jaké období se výdaj opakuje; může být ‘month’ (měsíc), ‘quarter’ (čtvrtletí), nebo ‘year’ (rok),
  • month – číslo měsíce v roce, ve kterém poprvé zaplatím,
  • day – číslo dne v měsíci, ve kterém zaplatím.
costs <- tribble(
  ~name,              ~cost,  ~cur, ~vatr, ~period,  ~month, ~day,
  "Účetnictví",     9300.00, "CZK",     0, "quarter",     1,   25,
  "O2",             1650.31, "CZK",    21, "month",       1,    1,
  "T-Mobile",        948.35, "CZK",    21, "month",       1,   10,
  "Toggl",            10.00, "USD",     0, "month",       1,    6,
  "Fakturoid",      5280.00, "CZK",    21, "year",        3,    1,
  "Basecamp",         12.00, "USD",     0, "month",       1,   19,
  "Screaming Frog",  149.00, "GBP",     0, "year",        1,   20
)
costs

Z tabulky costs si nyní vygeneruji tabulku cost_by_date, která bude v daném roce obsahovat jednotlivé výdaje v den, kdy je zaplatím.

Postupně provedu tyto kroky:

  1. Do objektu year si uložím rok, pro který chci plán spočítat.
  2. Funkcí left_join spojím tabulku nákladů s odpovídajícími kurzy z kurzovního lístku (je to dost podobné excelovské funkci VLOOKUP resp. SVYHLEDAT).
  3. Protože má kurz v kurzovním lístku strašně dlouhé jméno, přejmenuju ho funkcí rename na kratší.
  4. Pomocí mutate spočítám cenu v českých korunách a DPH.
  5. Pomocí mutate vytvořím sloupec date, který bude na každém řádku obsahovat celou posloupnost dat plateb jako seznam (list).
  6. Funkcí unnest_longer tyto seznamy dat plateb rozložím na samostatné řádky.
  7. A nakonec už jen funkcí select vyberu sloupce, které chci zahrnout do výsledku.
year <- 2022

costs_by_date <- costs |> 
  left_join(exchange_rates, by = c("cur" = "currency")) |> 
  rename(ex_rate = last_col()) |> 
  mutate(
    cost_czk = round(cost / ex_rate, 2),
    vat = round(cost_czk * (vatr / 100), 2)
  ) |> 
  rowwise() |> 
  mutate(
    date = list(seq(make_date(year, month, day), make_date(year, 12, 31), by = period))
  ) |> 
  ungroup() |> 
  unnest_longer(date) |> 
  select(date, name, cost_czk, vat)

costs_by_date

7.2.4 Plán příjmů

Nyní vytvořím tabulku odhadovaných výnosů revenue_by_date. Bude se jednat o měsíční příjmy, které dostanu v průměru okolo 15. dne následujícího měsíce. Vypočítají se z odhadu fakturovaných hodin vynásobených hodinovou sazbou. Počet fakturovaných hodin bude náhodně fluktuovat +/- 5 hodin okolo průměru. DPH je 21 %.

average_hours <- 100
hourly_rate <- 800

revenue_by_date <- tibble(
  date = seq(make_date(year, 1, 15), length = 12, by = "month"),
  revenue = round(runif(12, average_hours - 5, average_hours + 5)) * hourly_rate,
  vat =  revenue * 0.21
)

revenue_by_date

7.2.5 Spojený plán příjmů a výdajů

Zbývá spočítat cash flow a hezky ho zobrazit. Postup už podrobně vysvětlovat nebudu. Jakmile uvidíte funkci, kterou neznáte, dejte si na ni kurzor, zmáčkněte F1 a vše potřebné se dozvíte z nápovědy.

costs_by_date |> 
  transmute(
    date,
    cash_flow = -(cost_czk + vat)
  ) |> 
  bind_rows(
    revenue_by_date |> 
      transmute(
        date,
        cash_flow = revenue + vat
      )
  ) |> 
  group_by(date) |> 
  summarise(cash_flow = sum(cash_flow)) |> 
  ggplot(aes(x = date, y = cumsum(cash_flow))) + 
  geom_line() +
  scale_y_continuous(labels = scales::comma) +
  scale_x_date(date_breaks = "month", date_labels = "%b") +
  labs(x = "datum", y = "zůstatek Kč")

Má to jednu vadu, že? Nejsou tam odvody DPH. Tak ještě jednou a pro jistotu to rozdělím do dvou kroků, ať to jde lépe zkontrolovat.

Nejprve tabulka cash flow podle dní:

cash_flow_by_date <- costs_by_date |> 
  transmute(
    date,
    name,
    cash_flow = -(cost_czk + vat)
  ) |> 
  bind_rows(
    revenue_by_date |> 
      transmute(
        date,
        name = "příjem",
        cash_flow = revenue + vat
      )
  ) |> 
  
  # Zde přidám DPH na vstupu
  
  bind_rows(
    costs_by_date |> 
      group_by(
        date = quarter(date, type = "date_last") + 25,
        name = "DPH na vstupu"
      ) |> 
      summarise(cash_flow = sum(vat))
  ) |> 
  
  # Zde přidám DPH na výstupu
  
  bind_rows(
    revenue_by_date |> 
      group_by(
        date = quarter(date, type = "date_last") + 25,
        name = "DPH na výstupu"
      ) |> 
      summarise(cash_flow = -sum(vat))
  )
## `summarise()` has grouped output by 'date'. You can override using the
## `.groups` argument.
## `summarise()` has grouped output by 'date'. You can override using the
## `.groups` argument.
cash_flow_by_date

A nakonec graf:

cash_flow_by_date |> 
  group_by(date) |> 
  summarise(cash_flow = sum(cash_flow)) |> 
  ggplot(aes(x = date, y = cumsum(cash_flow))) + 
  geom_line() +
  scale_y_continuous(labels = scales::comma) +
  scale_x_date(date_breaks = "month", date_labels = "%b") +
  labs(x = "datum", y = "zůstatek Kč")

A to je všechno :-)