# 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 μ 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 σ is its <span class="text-success">standard deviation</span>. The <span class="text-success">variance</span> of the distribution is σ<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("μ", "σ", "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.