Să vorbim despre NA-S! | R-BLOGGERS

URMĂREȘTE-NE
16,065FaniÎmi place
1,142CititoriConectați-vă

(Acest articol a fost publicat pentru prima dată pe R pe biofunctorși a contribuit cu drag la R-Bloggers). (Puteți raporta problema despre conținutul de pe această pagină aici)


Doriți să vă împărtășiți conținutul pe R-Bloggers? Faceți clic aici dacă aveți un blog sau aici dacă nu.

Cavalerii care spun na

Arthur: Ei bine, ce vrei?
Head Knight: Vrem … o funcție de pastă care poate face față NA-S!

Un limbaj pentru calculul statistic trebuie să poată face față valorilor lipsă, iar R are diverse modalități de a face acest lucru. Voi trece pe scurt prin unele dintre ele, apoi voi propune o modalitate interesantă de a -l condimenta puțin.

Lipsa de lipsă este reprezentată cu NA valori sau-mai degrabă-non-valori. Fiecare tip vector poate conține valorile preconizate sau NA-s. După cum am menționat într -o postare anterioară, aceasta seamănă cu un
opțional sau pot fi Introduceți alte limbi. De cele mai multe ori, este utilizat pentru a reprezenta observațiile lipsă într -un set de date, dar puteți returna în mod egal un NA
Din funcția dvs. dacă nu este capabil să calculați rezultatul așteptat.

Pentru a ilustra acest lucru, să scriem o funcție care ia o literă mică și returnează cea anterioară din alfabet.

previous_letter <- function(x) {
    # Only consider the first letter of the fist string
    res <- letters(match(substr(x(1), 1, 1), letters) - 1)
    if (length(res) != 1L) NA_character_ else res
}

previous_letter("n")

## (1) "m"

previous_letter("a")

## (1) NA

previous_letter("#rstats")

## (1) NA

Dacă intrarea începe cu (b-z)va returna o literă minusculă, altfel NA.

Producerea valorilor lipsă nu este mare lucru. Tratarea lor este ceva mai complicată, iar R are diverse modalități de a face acest lucru.

Mai întâi! Puteți seta na.action Opțiune la nivel global, care poate fi sau nu utilizată de unele funcții sau obiecte. Acest lucru nu este recomandat, deoarece se bazează pe ceva specific configurației dvs., astfel încât acesta poate avea un comportament neintenționat într -un alt mediu. Explicit este mai bun decât implicit, ar trebui să spuneți ce doriți să faceți cu valorile lipsă.

Din fericire, The {stats} Pachetul vine cu câteva funcții de utilitate încorporate:

  • na.pass: lăsați valorile lipsă singure
  • na.omit: omiteți valorile lipsă și înregistrați unde au fost
  • na.exclus: La fel ca mai sus, dar o clasă diferită (vezi ajutor)
  • na.contiguu: Găsiți cea mai lungă perioadă consecutivă de valori care nu lipsesc într-un obiect
  • na.fail: Eroare de aruncare dacă lipsesc valori
Vezi exemple
vec_na <- c(1, 2, NA, 4)
na.pass(vec_na)

## (1)  1  2 NA  4

na.omit(vec_na)

## (1) 1 2 4
## attr(,"na.action")
## (1) 3
## attr(,"class")
## (1) "omit"

na.exclude(vec_na)

## (1) 1 2 4
## attr(,"na.action")
## (1) 3
## attr(,"class")
## (1) "exclude"

na.contiguous(vec_na)

## (1) 1 2
## attr(,"na.action")
## (1) 3 4
## attr(,"class")
## (1) "omit"

na.fail(vec_na) |> try()

## Error in na.fail.default(vec_na) : missing values in object

De asemenea, puteți crea funcții pentru a folosi aceste pe plan intern sau pentru a gestiona lipsă în alte moduri. Ai folosit vreodată median(na.rm = TRUE) în scripturile tale? Nu? Ce zici mean(na.rm = TRUE) Nu? sd apoi? Vedeți unde mă duc? Toate aceste (și alte) funcții au implementat NA manipularea separat. Ar putea exista o modalitate prin care dezvoltatorii s -ar putea concentra asupra sarcinii la îndemână (de exemplu, calcularea mediei unui vector de valori) și să nu fie nevoiți să le pese de ce să facă cu valorile lipsă?

Din această perspectivă, există două tipuri de funcții comune:

  • Rezumarea funcțiilor: Reduceți multe valori într -un singur scalar (de exemplu,
    mean() sau paste(collapse = " "))
  • Funcții simple: păstrați valorile pe tărâmul vectorial (de exemplu cumsum()
    sau paste(collapse = NULL))

Funcții simple

Funcția sumei cumulate menționate mai sus are o problemă cu valorile lipsă. Odată ce lovește un NAva produce NA-s pentru restul rezultatului.

cumsum(vec_na)

## (1)  1  3 NA NA

În cea mai mare parte, acesta este comportamentul necesar, dar uneori ar prefera să trateze NA ca lipsă și păstrează suma. S -ar putea gândi la un ambalaj funcțional pentru a evita NA-S într -unul sau mai multe argumente funcționale, calculați rezultatul și adăugați înapoi NA-S la pozițiile corecte. Să o numim dodge_NA. Putem restricționa ce argumente ale funcției sunt luate în considerare la căutarea valorilor lipsă și numai cazurile complete sunt utilizate în calcul (regulile de reciclare a vectorului se aplică dacă lungimile lor diferă).

dodge_NA() definiția funcției
# Modified from base::Vectorize()
dodge_NA <- function(FUN, these_args = arg.names)
{
    my_name <- match.call()(1L)
    my_args <- names(match.call()(-1L))
    arg.names <- as.list(formals(args(FUN)))
    arg.names(("...")) <- NULL
    arg.names <- names(arg.names)
    these_args <- as.character(these_args)
    if (!length(these_args))
        return(FUN)
    if (!all(these_args %in% arg.names))
        stop("must specify names of formal arguments for '", my_name, "'")
    collisions <- arg.names %in% my_args
    if (any(collisions))
        stop(sQuote("FUN"), " may not have argument(s) named ",
            paste(sQuote(arg.names(collisions)), collapse = ", "))
    rm(arg.names, collisions, my_args, my_name)
    (function() {
        FUNV <- function() {
            args <- lapply(as.list(match.call())(-1L), eval,
                parent.frame())

            names <- names(args) %||% character(length(args))

            to_consider <- (names %in% these_args) | names == ""

            max_length <- max(lengths(args(to_consider)))
            arg_df <- data.frame(lapply(args(to_consider), rep, length.out = max_length))

            has_fun <- complete.cases(arg_df)
            short_res <- do.call(
                what = FUN,
                args = c(as.list(arg_df(has_fun,,drop = FALSE)),
                         args(!to_consider)))
            # TODO: reconsider. this can give weird results if
            #       args(!to_consider) is not length 1

            long_res <- vector(
                mode = typeof(short_res),
                length = max_length
            )

            long_res(has_fun) <- short_res
            long_res(!has_fun) <- NA
            return(long_res)
        }
        formals(FUNV) <- formals(args(FUN))
        environment(FUNV) <- parent.env(environment())
        FUNV
    })()
}

Să ne uităm la câteva exemple!

cumsum_na <- dodge_NA(cumsum)
cumsum_na(vec_na)

## (1)  1  3 NA  7

Acum, suma cumulativă continuă dincolo de valorile lipsă.

paste_na <- dodge_NA(paste)

paste(vec_na, LETTERS(1:12), sep = "") |> noquote()

##  (1) 1A  2B  NAC 4D  1E  2F  NAG 4H  1I  2J  NAK 4L

paste_na(vec_na, LETTERS(1:12), sep = "") |> noquote()

##  (1) 1A   2B    4D   1E   2F    4H   1I   2J    4L

S -ar putea să existe motive perfect bune pentru a dori șirul „Na” în textul tău final, pur și simplu nu am întâlnit niciodată unul. I Întotdeauna A trebuit să curețe vectorii înainte de a intra în funcția de paste. Este destul de probabil ca să împachetez totul doar pentru a evita această problemă în viitor.

Rezumarea funcțiilor

Implementarea este puțin mai ușoară în acest caz, deoarece nu trebuie să adăugăm înapoi NA Valori în vectorul final (lungimea unuia). Restul este mai mult sau mai puțin același ca mai sus.

dodge_NA_collapse() definiția funcției
dodge_NA_collapse <- function(FUN, these_args = arg.names) {
    my_name <- match.call()(1L)
    my_args <- names(match.call()(-1L))
    arg.names <- as.list(formals(args(FUN)))
    arg.names(("...")) <- NULL
    arg.names <- names(arg.names)
    these_args <- as.character(these_args)
    if (!length(these_args))
        return(FUN)
    if (!all(these_args %in% arg.names))
        stop("must specify names of formal arguments for '", my_name, "'")
    collisions <- arg.names %in% my_args
    if (any(collisions))
        stop(sQuote("FUN"), " may not have argument(s) named ",
            paste(sQuote(arg.names(collisions)), collapse = ", "))
    rm(arg.names, collisions, my_args, my_name)
    (function() {
        FUNV <- function() {
            args <- lapply(as.list(match.call())(-1L), eval,
                parent.frame())

            names <- names(args) %||% character(length(args))

            to_consider <- (names %in% these_args) | names == ""

            max_length <- max(lengths(args(to_consider)))
            arg_df <- data.frame(lapply(
                args(to_consider),
                rep,
                length.out = max_length
            ))

            has_fun <- complete.cases(arg_df)

            res <- do.call(
                what = FUN,
                args = c(as.list(arg_df(has_fun,,drop = FALSE)),
                         args(!to_consider)))
            # TODO: reconsider. this can give weird results if
            #       args(!to_consider) is not length 1

            if (length(res) != 1L) warning(match.call()(1L), " produced vector of length ", length(res))

            return(res)
        }
        formals(FUNV) <- formals(args(FUN))
        environment(FUNV) <- parent.env(environment())
        FUNV
    })()
}

Putem găsi câteva exemple atunci când pasta simplă vă poate exploda în față. În aceste cazuri, ar fi bine să folosiți versiunea Dodge.

pastecollapse_na <- dodge_NA_collapse(paste)

paste(
    vec_na  + 1,
    "palms",
    sep = "",
    collapse = " "
)

## (1) "2palms 3palms NApalms 5palms"

pastecollapse_na(
    vec_na + 1,
    "palms",
    sep = "",
    collapse = " "
)

## (1) "2palms 3palms 5palms"

După cum știți, funcția medie folosește na.rm opţiune. Este un pic enervant să trebuiască să -l tastați de fiecare dată, dar să spunem că sunteți deja obișnuit cu asta.

Să implementăm o nouă funcție, plusminus. Acesta va adăuga chiar numere și va scădea numere ciudate într -un vector.

# plusminus :: ( int ) -> int
plusminus <- function(x) {
    Reduce(`+`, -x * (2L * (x %% 2L) - 1L))
}


pm_data <- round(runif(20) * 46)

# The final plus-minus sum
plusminus(pm_data)

## (1) -45

Plusminus este o funcție vectorizată, dar aș dori să vizualizez modul în care funcționează, așa că haideți să creăm o funcție pentru a complota suma care rulează Plusminus în fiecare poziție. Numerele care trebuie adăugate/scăzute sunt afișate de -a lungul curbei.

plot_running_sum() definiția funcției
plot_running_sum <- function(x, sumfun = plusminus) {
    pos <- seq_along(x)
    running_sum <- vapply(pos, (p) sumfun(x(1:p)), 0)
    reinj <- range(running_sum, na.rm = TRUE) # A-ha!
    plot(pos, running_sum, type = "b", ylim = reinj * c(1, 1.1))
    text(x = pos, y = running_sum + reinj(2) * 0.1, labels = x)
    return(invisible(running_sum))
}
plot_running_sum(pm_data)

Acum, ar trebui să adăugăm gestionarea erorilor la funcție? Implementăm na.rm
opțiune cu declarații în interiorul corpului … sau trebuie doar se eschiva Această corvoadă?

plusminus_NA_ready <- dodge_NA_collapse(plusminus)

# Break the data
pm_data(13) <- NA

# Can't handle it
plusminus(pm_data)

## (1) NA

# Can handle it
plusminus_NA_ready(pm_data)

## (1) -20

plot_running_sum(pm_data, sumfun = plusminus)

plot_running_sum(pm_data, sumfun = plusminus_NA_ready)

Sper că acest lucru ilustrează punctul meu: concentrați -vă pe logică și nu pe NA Manipulare, care este aceeași placă de cazan plictisitoare în multe funcții diferite.

Con (cl | f) usion

Deci, ce este Într -adevăr Mergi aici? Întreaga gândire a început cu monade. Într -un tărâm monadic, de obicei aveți nevoie de un bind (>>=) Funcția la calculele lanțului. Acest lucru se datorează faptului că funcțiile obișnuite pornesc de la Scalar și produc o valoare monadică (vezi ecuațiile de mai jos).

$$displaylines{fun :: a rightarrow m~b \ bind :: m~a rightarrow (a rightarrow m~b) rightarrow m~b}$$

În R, acest lucru este cu totul altul. Suntem deja în Vector+poate aterizează și, de obicei, nu -l lăsăm. De asemenea, funcțiile R iau rareori un singur argument. Mai multe argumente pot conține NA-s (valori opționale). Deci un clasic bind
Funcția ar fi prost potrivită în acest mediu. BIND ar funcționa doar la funcții unice (sau currie). Și pe deasupra, ar adăuga un alt operator de infix, care poate enerva unii (mulți?) Oamenii.

În schimb, am decis să merg pentru un înveliș funcțional. Este nevoie de o funcție care să poată gestiona doar valorile de intrare care nu lipsește și să o îmbrace cu capacitatea de a le evita.

$$wrap :: (a rightarrow ldots rightarrow b rightarrow m~c) rightarrow (m~a rightarrow m~ldots rightarrow b rightarrow m~c)$$

Această abordare are unele dezavantaje, de exemplu, necesitatea a două ambalaje pentru cele două tipuri de funcții (rezumare și simplă). Pe de altă parte, va face față destul de bine cu unele specifice R, iar funcția rezultată poate fi pur și simplu conductă cu o țeavă nativă sau magrittr, așa cum am fi obișnuiți.

În continuare, aș dori să verific dacă acest lucru poate fi extins la celălalt monad care este originar la R, vectorul.

Dominic Botezariu
Dominic Botezariuhttps://www.noobz.ro/
Creator de site și redactor-șef.

Cele mai noi știri

Pe același subiect

LĂSAȚI UN MESAJ

Vă rugăm să introduceți comentariul dvs.!
Introduceți aici numele dvs.