---
# System prepended metadata

title: ShinyRV - Proiect Probabilități și Statistică

---

# ShinyRV - Proiect Probabilități și Statistică

## Introducere

Proiectul de față urmărește crearea unei aplicații web (folosind pachetul Shiny) care permite lucrul cu variabile aleatoare discrete și continue. Acesta a fost realizat de Anghel Alin, Blănar George și Micudă Andrei.

## Dependențele Proiectului

Aplicația folosește multiple pachete oferite de R prin CRAN, utilitatea fiecăruia fiind detaliată mai jos:

* [`shiny`](https://cran.r-project.org/web/packages/shiny/index.html) - framework-ul de bază cu ajutorul căruia a fost realizată aplicația
* [`hash`](https://cran.r-project.org/web/packages/hash/index.html) - permite definirea unor structuri similare cu obiectele de tip JSON, utilizat pentru stocarea mai usoara a datelor
* [`Rlab`](https://cran.r-project.org/web/packages/Rlab/index.html) -
* [`stringr`](https://cran.r-project.org/web/packages/stringr/index.html) - folosit pentru parsarea input-ului dat de utilizator, precum și realizarea diferitor operații pe string-uri
* [`discreteRV`](https://cran.r-project.org/web/packages/discreteRV/index.html) - lucru cu variabile aleatoare discrete, folosit la cerinta 12

De asemenea, am utilizat diverse surse de inspirație care includ, dar nu se limitează la:

* [Wikipedia](https://en.wikipedia.org/wiki/List_of_probability_distributions) și [Statistics How To](https://www.statisticshowto.com/) pentru rezumatul distribuțiilor din Galerie
* [Documentația](https://www.rdocumentation.org/packages/shiny/versions/1.6.0) și [tutorialul introductiv](https://shiny.rstudio.com/tutorial/) oferite de Shiny
* [Documentația R](https://www.rdocumentation.org)
* [Stack Overflow](https://stackoverflow.com/) pentru diferitele probleme întâmpinate pe parcursul realizării cerințelor

## Comentarea codului

### Cerinte Abordate:
    1, 2, 3, 5, 6, 7, 8, 12



### Cerința 1.
Aplicația conține 15 distribuții predefinite care sunt ținute într-un obiect de tip `hash` de forma:

```r=
data_distributions <- hash(
  "Normal" = {hash(
      "desc_HTML" = '
        <div class="panel panel-body">
          <p>
            In probability theory, a <b>normal</b> (or <b>Gaussian</b> or <b>Gauss</b> or <b>Laplace–Gauss</b>) <b>distribution</b> is a type of <span class="text-success">continuous probability distribution</span> for a real-valued random variable. The general form of its probability density function is
          </p>
          <img src="https://wikimedia.org/api/rest_v1/media/math/render/svg/00cb9b2c9b866378626bcfa45c86a6de2f2b2e40" class="center-block" aria-hidden="true" style="vertical-align: -2.838ex; width:24.446ex; height:6.676ex;" />
          <br />
          <p>
            The parameter &mu; is the <span class="text-success">mean</span> or <span class="text-success">expectation</span> of the distribution (and also its <b>median</b> and <b>mode</b>), while the parameter &sigma; is its <span class="text-success">standard deviation</span>. The <span class="text-success">variance</span> of the distribution is &sigma;<sup>2</sup>.
          </p>

          <p>
            Normal distributions are important in statistics and are often used in the natural and social sciences to <b>represent real-valued random variables whose distributions are not known</b>. Their importance is partly due to the <span class="text-success">central limit theorem</span>. It states that, under some conditions, the average of many samples (observations) of a random variable with finite mean and variance is itself a random variable—whose distribution converges to a normal distribution as the number of samples increases. 
          </p>
        </div>
    ',
      "variables" = data.frame(
        "name" = c("&mu;", "&sigma;", "a", "b"),
        "input_id" = c("mean", "std_dev", "a", "b"),
        "default_value" = c(0, 1, -5, 5)
      ))},
  "Bernoulli" = {hash(...)},
  ...
)
```

Astfel, pentru fiecare distribuție avem definite o descriere sub formă de HTML, precum si un `data.frame` cu variabilele care o parametrizează. `name` reprezintă valoarea care va fi dată `label`-ului pentru input-ul respectiv, `input_id` -- id-ul prin care va fi identificat input-ul variabilei, iar celelalte valori reprezintă valoarea implicită și limitele între care se va încadra fiecare variabilă.

Pe baza cheilor din `hash`-ul anterior, se generează un `selectInput` care permite selecția unei distribuții pe baza numelui.

```r=
# Drop-down selection box for which data set
  output$choose_distribution <- renderUI({
    selectInput(inputId = "distribution", "Distribution", keys(data_distributions), selected = "Normal")
  })
```

Apoi, pe baza distribuției selectate, sunt generate `input`-uri pentru variabilele ce parametrizează acea distribuție.

```r=
# Inputs for the variables of the selected distribution
  output$choose_values <- renderUI({
    # If missing input, return to avoid error later in function
    if(is.null(input$distribution))
      return()
    
    # Get the data set with the appropriate name
    selected_distribution <- data_distributions[[paste(input$distribution)]]
    
    variable_inputs <- apply(selected_distribution$variables, 1, function(row){
      name <- row[1]
      input_id <- row[2]
      default_value <- row[3]
      
      min <- NA
      max <- NA
      step <- NA
      if(length(row) > 3) {
        min <- row[4]
        max <- row[5]
        step <- row[6]
      }
      
      numericInput(inputId = paste("var_", input_id, sep = ""), label = HTML(name), value = as.numeric(default_value), min = min, max = max, step = step)
    })
    
    # Create the checkboxes and select them all by default
  })
```

Pentru ca informațiile din stânga să fie actualizate doar la apăsarea butonului **Update**, informațiile legate de distribuția aleasă și valorile parametrilor sunt salvate într-un `hash` în cadrul unei funcții `eventReactive`.

```r=
distribution_plot_data <- eventReactive(c(input$update_plot, input$n_points), {
    distribution_name = paste(input$distribution) # Concatenate vectors after converting to character.
    
    # if the data didn't load yet (update button hasn't been clicked)
    # fill in the data for N(0,1)
    if(length(distribution_name) == 0){
      distribution_name <- 'Normal'
      points <- seq(-5, 5, length.out = 20)
      
      hash(
        "name" = distribution_name,
        "desc" = data_distributions[[distribution_name]]$desc_HTML,
        "dens_mass_plot_values" = dnorm(points, 0, 1),
        "distribution_plot_values" = pnorm(points, 0, 1),
        "points" = points,
        "statistics" = hash(
          "Mean" = 0,
          "Median" = 0,
          "Variance" = 1,
          "Standard deviation" = 1
        ),
        "probability_func" = pnorm)
    }
    else {
      if(distribution_name == 'Normal') {
        points <- seq(input$var_a, input$var_b, length.out = input$n_points)
        std_dev <- input$var_std_dev
        mean <- input$var_mean
        
        hash(
          "name" = distribution_name,
          "desc" = data_distributions[[distribution_name]]$desc_HTML,
          "dens_mass_plot_values" = dnorm(points, mean, std_dev),
          "distribution_plot_values" = pnorm(points, mean, std_dev),
          "points" = points,
          "statistics" = hash(
            "Mean" = mean,
            "Median" = mean,
            "Variance" = std_dev,
            "Standard deviation" = sqrt(std_dev)
          ),
          "probability_func" = pnorm)
      }
      else if(distribution_name == 'Log-normal') {...}
      ...
    } 
  })
```

Astfel, pentru output-urile din partea stângă, ne folosim de obiectul `distribution_plot_data` pentru generare, astfel încât acestea vor fi actualizate doar când acesta își schimbă structura.

```r=
output$desc <- renderText({distribution_plot_data()$desc})
  
output$density_mass_plot <- renderPlot({
  data <- distribution_plot_data()
  plot_color <- '#428bca'
  
  # we customize some of the plots
  if(data$name == 'Uniform')
  {
    a <- data$interval_points$begin
    b <- data$interval_points$end
    # we center the interval and make it take 70% of the whole x axis
    offset_x <-3 * (b - a) / 14
    plot(
      data$points, data$dens_mass_plot_values,
      xlim = c(a - offset_x, b + offset_x),
      ylim = c(0, 10 / (8 * (b - a))),
      type = 'l',
      col=plot_color,
      lwd = 2)

    abline(
      h = 1 / (b-a),
      col = '#555555',
      lty = 'dashed'
    )

    text(
      x = a - offset_x,
      y = 1 / (b-a),
      expression(frac(1, b-a)),
      pos = 3,
      cex = 0.8,
      col = '#555555'
    )

    segments(
      x0 = c(a - offset_x - 100, b),
      y0 = 0,
      x1 = c(a, b + offset_x + 100),
      y1 = 0,
      col = plot_color,
      lwd = 2)

    segments(
      x0 = c(a, b),
      y0 = 0,
      x1 = c(a, b),
      y1 = 1 / (b-a),
      col = plot_color,
      lty = 'dashed'
    )

    points(
      x = c(a, b),
      y = c(1 / (b-a), 1 / (b-a)),
      col = plot_color,
      pch = 19
    )

    rect(
      xleft = a,
      ybottom = 0,
      xright = b,
      ytop = 1 / (b-a),
      border = 'transparent',
      density = 5,
      angle = 30,
      col = '#555555'
    )
  }
  else if(data$name == 'Poisson') { ... }
  else
  {
    plot(
      data$points, data$dens_mass_plot_values,
      col=plot_color
      )
  }
})

output$statistics_table <- renderTable({
  data <- distribution_plot_data()

  # we have to convert our 'statistics' hash to a data frame so that it can be rendered to the UI
  table <- data.frame(matrix(ncol = 2, nrow = 0))
  col_names <- c("Stat", "Value")
  colnames(table) <- col_names

  for(key in keys(data$statistics))
  {
    table[nrow(table) + 1,] <- list(key, data$statistics[[key]])
  }

  table
})

...
```

---

### Cerința 2.
Apăsând pe butonul "New Distribution"

```r=
observeEvent(input$newdist, {
showModal(newRv())
})
``` 

utilizatorului îi apare un modal unde trebuie introduși doi vectori <br/>
    
```r=
textInput("values", label="Values"),
textInput("probabilities", label="Probabilities"),
```

(outcomes și probabilities). <br/>
La apăsarea butonului Ok cele doua câmpuri sunt verificate,

```r=
  v <- try(eval(parse(text=input$values)), TRUE)
  p <- try(eval(parse(text=input$probabilities)), TRUE)

  # verfica sa fie bun inputul
  if((typeof(v) != "double" && typeof(v) != "integer") || typeof(p) != "double" && typeof(p) != "integer") {
    # in cazul in care nu este bun afiseaza pe ecran acest lucru
    showModal(newRv(failed = TRUE))
  } 
```
    
iar în cazul în care sunt valide sunt introduse în lista de V.A. 
  
```r=
matr <- validate_probability(v, p)
...
userDist[[length(userDist) + 1]] <<- matr
```

Ulterior lista este folosită pentru a afișa distribuțiile create de utilizator dând click pe butonul "Show user created distribution".

```r=
ShowGraphs <- function(rv) {
modalDialog(
  title = "Distributii adaugate de utilizator",
  renderPlot(plot(x = rv[1,], y = rv[2,]))
)
}

observeEvent(input$showdist,{
for(i in userDist) {
  showModal(ShowGraphs(i))
}
})
```

---

### Cerința 3.
Aceasta cerința se gasește in tab-ul "Working with events", ca input sunt introduse probabilitățile `P(a)` și `P(b)` (În cazul în care Not Known este selectat sunt și alte input-uri)

```r=
    output$events_calculator <- renderUI({
        if(is.null(input$relation))
              return()
        # pentru fiecare optiune din lista Idependente, Incompatibile, Not Known, generam inputuri si outputuri pentru calcularea diferitelor     probabilitati
        if(input$relation == "Independent"){
             list(numericInput(inputId ="Pa", label = "P(a)",value = 0.5, min=0,max=1,step =0.05 ),
            numericInput(inputId ="Pb", label = "P(b)",value = 0.5, min=0,max=1,step =0.05 ))
        }else if( input$relation == "Incompatible") {
           list(numericInput(inputId ="Pa", label = "P(a)",value = 0.5, min=0,max=1,step =0.05 ),
           numericInput(inputId ="Pb", label = "P(b)",value = 0.5, min=0,max=1,step =0.05 ))
        }else if(input$relation == "Not known") {
           list(numericInput(inputId ="Pa", label = "P(a)",value = 0.5, min=0,max=1,step =0.05 ),
           numericInput(inputId ="Pb", label = "P(b)",value = 0.5, min=0,max=1,step =0.05 ),
           numericInput(inputId ="PaUPb", label = "P(a U b)", value =0.5,min = 0 , max =1,step = 0.05),
           numericInput(inputId ="PacPb",label ="P(a|b)", value=0.5,min=0,max=1,step=0.05))
    }
})
```

Când utilizatorul apasă pe calculate, folosind formulele de la curs (precum Poincare și formula probabilității condiționate etc.) sunt calculate celelalte probabilități afișate mai jos.

```r=
pa <- input$Pa
pb <- input$Pb
if(input$relation == "Incompatible"){

  output$Intersectie <-renderText(0) 
  output$Reuniune <-renderText(pa+pb)
  output$Restrictie <-renderText("-") 

}else if(input$relation == "Independent"){

  output$Intersectie <-renderText(pa*pb) 
  output$Reuniune <-renderText(pa+pb- pa*pb)
  output$Restrictie <-renderText(pb) 

}else if(input$relation == "Not known"){

  reun <- input$PaUPb
  restr<- input$PacPb

  output$Intersectie <-renderText(pa+pb-reun) 
  output$Reuniune <-renderText(reun)
  output$Restrictie <-renderText((restr*pb)/pa) 

}
```
---
### Cerința 5.
Tab-ul "Show this Discrete R.V." al galeriei de distribuții este responsabil pentru afișarea unei variabile discrete a cărei repartiție e aleasă de utilizator din galerie cu posibilitatea de a alege valoare de început. Am ales aceasta abordare de rezolvare pentru a ne îndepărtă puțin de spiritul [cerinței 2](#Cerința-2.) deoarece cerința menționată cuprinde și afișarea unei variabile discrete.

Pentru a prelua input-ul (valoarea de început) am folosit `eventReactive` prin `show_drv` care așteaptă ca butonul "="(cu id-ul `svalapply`) să fie apăsat după ce a fost introdus un input. După apăsare valoarea introdusă este preluată și stocată în variabila `stârvalue`.

```r=
show_drv <- eventReactive(input$svalapply,{
    starvalue <- input$svalinp
  })
    
```
`show_drv` va fi apelat ulterior de `startv` pentru a genera plot-ul care servește drept reprezentare grafică a variabilei discrete. În primul rând aflăm prin variabila `data` care este distribuția curentă din galerie. Dacă valoare este 0 (cel puțin în cazul geometrice) datele plot-ului nu se schimbă iar dacă valoarea este diferită de 0 vom modifica corespunzător axele x și y ale plot-ului.

```r=
output$drv_hist <- renderPlot({
    data<- distribution_plot_data()
    startv <- show_drv()
    plot_color <- '#428bca'
    if (startv == 0){
      a <- data$points
      b <-data$dens_mass_plot_values
    }else
    {
      a <- startv:tail(data$points,n=1)
      b <- data$dens_mass_plot_values[-(1: (length(data$dens_mass_plot_values) -length(a)))]
    }
    plot(
      a,b
    )
  })
```
In final plot-ul este afișat in UI prin `plotOutput`

```r=
{
    plotOutput(
      outputId = "drv_hist"
    )
}
```
**Observație** : Această funcționalitate a fost lăsată valabilă pentru toate distribuțiile însă este adecvată pentru cele discrete.

---
### Cerința 6.
Tab-ul "Probability calculator" al galeriei de distribuții permite introducerea unui eveniment pe baza căruia se va calcula probabilitatea asociată acestuia în contexul distribuției selectate.

La fel ca în cadrul [cerinței 1](#Cerința-1.), vom folosi funcția `eventReactive` pentru a actualiza valorile doar la apăsarea butonului "=". Astfel, vom trimite mai departe informațiile distribuției stocate în `distribution_plot_data` și vom parsa input-ul oferit de utilizator.

```r=
probability_calculator_data <- eventReactive(input$calculate_probability, {
    distribution_data <- distribution_plot_data()
    raw_probability_input <- input$probability_calc_input
    
    hash(
      "distribution_data" = hash("name" = distribution_data$name),
      "probability_data" = parseProbabilityInput(input$probability_calc_input)
    )
  })
```

Parsarea efectivă se realizează în cadrul mai multor funcții auxiliare care utilizează funcții predefinite în pachetul `stringr`, precum `str_detect` pentru validarea input-ului folosind expresii regulate sau funcții utilitare cum ar fi `str_extract_all` sau `str_split`

```r=
# matches "u<=X" or "X<=u"
isSingleInequality <- function(str) {
  str_detect(str, "^X[<>]?=-?[0-9]+.?[0-9]*$|^X[<>]-?[0-9]+.?[0-9]*$")
}

# matches "u<=X<=v"
isDoubleInequality <- function(str) {
  str_detect(str, "^-?[0-9]+.?[0-9]*<=?X<=?-?[0-9]+.?[0-9]*$")
}

parseNonConditionalProbability <- function(str) {
  # matches "u<=X" or "X<=u"
  if(isSingleInequality(str)) {
    hash(
      "has_error" = FALSE,
      "is_conditional" = FALSE,
      "is_interval" = FALSE,
      "operator" = str_extract(str, "[<>]?=|[<>]"),
      "value" = as.numeric(str_extract(str, "-?[0-9]+.?[0-9]*"))
    )
  }
  # matching "u<=X<=v"
  else if(isDoubleInequality(str)) {
    hash(
      "has_error" = FALSE,
      "is_conditional" = FALSE,
      "is_interval" = TRUE,
      "operators" = str_extract_all(str, "<=?"),
      "values" = as.numeric(unlist(str_extract_all(str, "-?[0-9]+.?[0-9]*")))
    )
  }
  else {
    hash("has_error" = TRUE)
  }
}

parseProbabilityInput <- function(rawInput) {
  # if string doesn't contain "|" we check if it's a simple probability
  if(!str_detect(rawInput,"\\|")) {
    parseNonConditionalProbability(rawInput)
  }
  else {
    probs <- unlist(str_split(rawInput, "\\|"))
    left_prob <- parseNonConditionalProbability(probs[1])
    right_prob <- parseNonConditionalProbability(probs[2])
    
    # if we successfully parsed both parts of the conditional probability
    if(left_prob$has_error == FALSE & right_prob$has_error == FALSE) {
      hash(
        "has_error" = FALSE,
        "is_conditional" = TRUE,
        "left_prob" = left_prob,
        "right_prob" = right_prob
      )
    }
    else {
      hash("has_error" = TRUE)
    }
  }
}
```

Sunt acceptate input-uri de forma `X<3`, `X<=-2`, `X>5`, `X>=12`, `-3<X<=12`, precum și probabilități condiționate precum `X>5|X>0`. Astfel, pentru un input de forma `X<5|X>=0` în cadrul distribuției normale, obiectul `probability_calculator_data` va conține următoarele informații:

```bash=
<hash> containing 2 key-value pair(s).
  distribution_data : <hash> containing 1 key-value pair(s).
      name : Normal
  probability_data : <hash> containing 4 key-value pair(s).
      has_error : FALSE
      is_conditional : TRUE
      left_prob : <hash> containing 5 key-value pair(s).
          has_error : FALSE
          is_conditional : FALSE
          is_interval : FALSE
          operator : <
          value : 5
      right_prob : <hash> containing 5 key-value pair(s).
          has_error : FALSE
          is_conditional : FALSE
          is_interval : FALSE
          operator : >=
          value : 0
```

Ulterior, în cadrul output-ului, ne vom folosi de funcția `calculateProbability` pentru a transforma `hash`-ul anterior într-o probabilitate.

```r=
output$probability_calc_output <- renderText({
  data <- probability_calculator_data()
  calculateProbability(data, input)
})

probabilityWrapper <- function(x, distribution_data, input) {
  
  distribution_data$name <- unlist(distribution_data$name)

  if(distribution_data$name == "Normal") {
    mean <- isolate(input$var_mean)
    std_dev <- isolate(input$var_std_dev)
    
    pnorm(x, mean, std_dev)
  }
  else if(distribution_data$name == 'Log-normal') { ... }
  ...
}

calculateNonConditionalProbability <- function(data, input) {
  if(data$probability_data$is_interval == FALSE){
    # input ~ "X<=u"
    if(data$probability_data$operator %in% c("<", "<=")) {
      probabilityWrapper(data$probability_data$value, data$distribution_data, input)
    }
    # input ~ "X>=u"
    else{
      1 - probabilityWrapper(data$probability_data$value, data$distribution_data, input)
    }
  }
  # input ~ "u<=X<=v"
  else{
    probabilityWrapper(data$probability_data$values[2], data$distribution_data, input) - probabilityWrapper(data$probability_data$values[1], data$distribution_data, input)
  }
}

intersectProbabilities <- function(lprob, rprob) {
  if(!lprob$is_conditional & !rprob$is_conditional) {
    if(lprob$operator %in% c("<", "<=") & rprob$operator %in% c("<", "<=")) {
      hash(
        "has_error" = FALSE,
        "is_conditional" = FALSE,
        "is_interval" = FALSE,
        "operator" = ifelse(lprob$value<rprob$value, lprob$operator, rprob$operator),
        "value" = ifelse(lprob$value<rprob$value, lprob$value, rprob$value)
      )
    }
    else if(lprob$operator %in% c(">", ">=") & rprob$operator %in% c(">", ">=")) {
      hash(
        "has_error" = FALSE,
        "is_conditional" = FALSE,
        "is_interval" = FALSE,
        "operator" = ifelse(lprob$value<rprob$value, rprob$operator, lprob$operator),
        "value" = ifelse(lprob$value<rprob$value, rprob$value, lprob$value)
      )
    }
    else if(lprob$operator %in% c("<", "<=") & rprob$operator %in% c(">", ">=")) {
      hash(
        "has_error" = FALSE,
        "is_conditional" = FALSE,
        "is_interval" = TRUE,
        "operators" = c(rprob$operator, lprob$operator),
        "values" = c(rprob$value, lprob$value)
      )
    }
    else {
      hash(
        "has_error" = FALSE,
        "is_conditional" = FALSE,
        "is_interval" = TRUE,
        "operators" = c(lprob$operator, rprob$operator),
        "values" = c(lprob$value, rprob$value)
      )
    }
  }
  else {
    hash( "has_error" = TRUE )
  }
}

calculateProbability <- function(data, input) {
  if(!data$probability_data$is_conditional)
  {
    calculateNonConditionalProbability(data, input)
  }
  else
  {
    intersectionProbability <- intersectProbabilities(data$probability_data$left_prob, data$probability_data$right_prob)
    
    intersection_data = hash(
      "distribution_data" = hash("name" = data$distribution_data$name),
      "probability_data" = intersectionProbability
    )
    
    r_data = hash(
      "distribution_data" = hash("name" = data$distribution_data$name),
      "probability_data" = data$probability_data$right_prob
    )
    
    #x1 <- calculateNonConditionalProbability(intersection_data, input)
    #x2 <- calculateNonConditionalProbability(r_data, input)
    calculateNonConditionalProbability(intersection_data, input) / calculateNonConditionalProbability(r_data, input)
  }
}
```

Pentru `hash`-ul dar ca exemplu mai devreme, funcția întoarce valoarea `0.9999994`.

**Observație**: Funcția `calculateProbability` permite calculul doar pentru variabile **continue**.

---
### Cerința 7.
Tab-ul "Discrete RV - g(X)" din meniul principal al aplicației permite introducerea valorilor a doi vectori -- pentru valorile și probabilitățile variabilei discrete -- separate prin spațiu și a unei funcții de transformare `g(x)` pentru calcularea noii variabile discrete g(X).

În cadrul unei funcții `eventReactive` vom reține cei doi vectori -- de valori și de probabilități, parsând inputul si transformându-l în numere:

```r=
rv_transform_data <- eventReactive(input$rv_transform_update, {
  x <- unlist(lapply(str_split(input$var_x_transform, " "), function(x) as.numeric(x)))
  p <- unlist(lapply(str_split(input$prob_x_transform, " "), function(x) as.numeric(x)))

  hash(
    "x" = x,
    "p" = p,
    "g" = input$g_x_transform
  )
})
```

Astfel, pentru input-ul `X=-1 0 1`, `p=0.2 0.6 0.2` și `g(x)=x^2`, obiectul `rv_transform_data` va conține următoarele informații:

```bash=
<hash> containing 3 key-value pair(s).
  g : x^2
  p : 0.2 0.6 0.2
  x : -1  0  1
```

Apoi, pentru afișarea output-ului, din moment ce avem de-a face cu v.a. discrete, probabilitățile vor rămâne neschimbate atunci când aplicăm `g(x)`, deci trebuie doar să transformăm valorile lui `x` și să avem grijă să adunăm probabilitățile valorilor care au devenit egale în urma transformării.

```r=
output$rv_transform_output <- renderText({
  data <- rv_transform_data()
  if(length(data$x) != length(data$p)) {
    "Input must have the same length"
  }
  else {
    func <- function(x) { eval(parse(text=data$g)) }
    raw_new_x <- unlist(lapply(data$x, func))
    new_x <- c()
    new_p <- c()
    for(i in seq(from = 1, to = length(raw_new_x), by = 1)) {
      val_curr <- raw_new_x[i]
      if(val_curr %in% new_x) {
        pos <- match(val_curr, new_x)
        new_p[pos] <- new_p[pos] + data$p[i]
      }
      else {
        new_x <- append(new_x, val_curr)
        new_p <- append(new_p, data$p[i])
      }
    }
    new_data <- data.frame(new_x, new_p)
    new_data <- new_data[order(new_data$new_x),]
    output_text <- paste(str_c(new_data[, 1], collapse=" "))
    output_text <- paste(output_text, "\n", str_c(new_data[, 2], collapse=" "))
    output_text
  }
})
```

Astfel, pentru input-ul dat ca exemplu mai devreme, output-ul va fi:

```bash=
0 1 
0.6 0.4
```

---

### Cerința 8.
Tab-ul "Applying Function" al galeriei de distribuții este cel responsabil pentru aplicarea funcțiilor unei variabile aleatoare continue a cărei repartiție este aleasă de utilizator din Galerie. 

Pornim de la un `textInput` care va fi funcția primită de la tastatură (text care va fi preluat de variabilă `raw_input`). Pentru verificarea corectitudinii funcției preluate de la tastatură vom lua spre utilizare o funcție inline anonimă care va parsa input-ul și apoi îl va evalua.

```r=
  apply_function <- eventReactive(input$function_apply, {
    
    distribution_data <- distribution_plot_data()
    
    raw_input <- input$function_input
    
    primary_func <- function(x){eval(parse(text=raw_input))}
    #pentru fiecare distributie continua discretitez intervalul si aplic functia primita
    if(distribution_data$name =="Normal"){
      values<- c(-1000:1000)
      probs<- dnorm(values)
    }else if (distribution_data$name == "Chi-square"){
      values<- c(0:1000)
      probs<- dchisq(values,distribution_data$statistics$Mean)
    }else if (distribution_data$name == "Exponential"){
      values<-c(0:1000)
      probs <- dexp(values,distribution_data$statistics$Mean^(-1))
    }
   
    sample<- random_variable <- matrix(data=c(values,probs), nrow=2, byrow=TRUE)
    result<-applyFunction(sample,primary_func)

  
```
Implementarea este realizată doar pentru 3 repartiții continue, mai sus menționate. Pentru aplicarea unei funcții pe o distribuție a trebuit să discretizăm acea distribuție. Această discretizare este realizată prin atribuirea unui interval mare de valori. După transformarea distribuției într-o matrice vom folosi funcția `applyFunction` (disponibilă în fișierul *discreteRV.r* )

```r=
applyFunction <- function(m, f) {
  m[1,] = f(m[1,])
  m
}

```
După aplicarea funcției asupra distribuției ne vom folosi de `getVariance` și de `getMean` (disponibile în fișierul *discreteRV.r* ) pentru a ajunge la rezultatele dorite. 

```r=
getMean <- function(m) {
  sum(m[2,] * m[1,])
}

getVariance <- function(m) {
  getMean(applyFunction(m, function(x) x**2)) - getMean(m) ** 2
}

```

În final prin `renderText` vom afișa rezultatele.

```r=
  
  output$var_output <- renderText({
    data <- apply_function()
    
    round(getVariance(data),digits = 0)
    
  })
  output$mean_output <- renderText({
    data <- apply_function()
    
    round(getMean(data),digits = 0)
    
  })
  
```


---

### Cerința 12.
Într-o manieră similară cu cerința 2, sunt introduse câmpurile outcomes şi probabilities pentru variabilele X si Y.

```r=
var_x <- try(eval(parse(text=input$var_x)), TRUE)
    prob_x <- try(eval(parse(text=input$prob_x)), TRUE)

    var_y <- try(eval(parse(text=input$var_y)), TRUE)
    prob_y <- try(eval(parse(text=input$prob_y)), TRUE)

    rv_x <- try(RV(outcomes=var_x, probs=prob_x))
    rv_y <- try(RV(outcomes=var_y, probs=prob_y))
```

La apăsarea butonului "Update" sunt calculate suma, diferența produsul şi raportul dintre acestea doua,

```r=
    generateTable <- function(x, y) {
        # tabel folosit pentru frontend-ul aplicatiei
        table <- data.frame(matrix(ncol = 3, nrow = 0))
        col_names <- c("Operation", "Value", "Probabilities")
        colnames(table) <- col_names

        # calculam diferite operatii intre cele 2 V.A.
        sum <- x + y
        dif <- x - y
        prod <- x * y
        ratio <- x / y

        # adaugam in tabel V,A. noi obtinute
        table[1,] <- list("Sumation",toString(outcomes(sum)), toString(round(probs(sum), 3)))
        table[2,] <- list("Difference",toString(outcomes(dif)), toString(round(probs(dif), 3)))
        table[3,] <- list("Product",toString(outcomes(prod)), toString(round(probs(prod), 3)))
        table[4,] <- list("Ratio",toString(outcomes(ratio)), toString(round(probs(ratio), 3)))

        table
  }
```
    
și afișate sub forma unui tabel
    
```r=
    table <- generateTable(rv_x, rv_y)
    output$rv_operations <- renderTable(table)
```

Limite: În cazul în care dăm submit fără să avem câmpurile valide, programul va da eroare


## Dificultăți în realizarea cerințelor
1. O problema întampinată de toți trei a fost cea de parsare a inputurilor complexe (exemple ar fi inputurile în care ceream vectori, funcții care trebuiau aplicate pe distribuții sau input-uri care implicau lucruri cu evenimente). În cazul input-urilor care trebuiau evaluate, precum erau vectorii funcțiile folosite au fost `exec` și `eval`, cu ajutorul cărora puteam executa cod R dintr-un string. Pentru validarea input-urilor complexe (calculul unor probabilități condiționate), am utilizat expresii regulate împreună cu funcțiile oferite de `stringr`.
2. Acomodarea cu workflow-ul impus de Shiny, întrucât crearea unei aplicații web în R este destul de diferită și restrictivă comparativ cu alte framework-uri precum React sau Angular
3. Găsirea unei soluții pentru rezolvarea cerinței 8 a fost destul de dificilă. După câteva ore de încercat variante de răspuns unul dintre noi a venit cu ideea de discretizare iar pentru aceasta a fost nevoie de implementarea unui fișier seapărat .R (*discreteRV.r*) pentru gestionarea aplicării de funcții.
4. Rezolvarea diverselor bug-uri care țin de sintaxă. R nu oferă descrieri explicite pentru problemele de rulare iar atunci când, de exemplu, o virgulă era în plus toată aplicația crăpa.