Czy rodzina "* apply " naprawdę nie jest wektoryzowana?

Więc jesteśmy przyzwyczajeni do mówienia każdemu nowemu użytkownikowi, że "apply nie jest wektoryzowany, sprawdź Patrick Burns R Inferno Circle 4 " który mówi (cytuję):

Powszechnym odruchem jest użycie funkcji w rodzinie apply. to nie jest wektoryzacja, to ukrywanie pętli. Funkcja apply ma pętlę for w jego definicja. Funkcja lapply zakopuje pętlę, ale wykonanie czasy wydają się być mniej więcej równe wyraźnemu dla pętla.

Rzeczywiście, szybkie spojrzenie na apply kod źródłowy ujawnia pętlę:

grep("for", capture.output(getAnywhere("apply")), value = TRUE)
## [1] "        for (i in 1L:d2) {"  "    else for (i in 1L:d2) {"

Jak na razie Ok, ale spojrzenie na lapply lub vapply faktycznie odsłania zupełnie inny obraz:

lapply
## function (X, FUN, ...) 
## {
##     FUN <- match.fun(FUN)
##     if (!is.vector(X) || is.object(X)) 
##        X <- as.list(X)
##     .Internal(lapply(X, FUN))
## }
## <bytecode: 0x000000000284b618>
## <environment: namespace:base>

Więc najwyraźniej nie ma tam pętli R for, a raczej wywołują wewnętrzną funkcję zapisu C.

Szybkie spojrzenie w królika dziura pokazuje prawie to samo zdjęcie

Ponadto Weźmy na przykład funkcję colMeans, który nigdy nie został oskarżony o brak wektoryzacji

colMeans
# function (x, na.rm = FALSE, dims = 1L) 
# {
#   if (is.data.frame(x)) 
#     x <- as.matrix(x)
#   if (!is.array(x) || length(dn <- dim(x)) < 2L) 
#     stop("'x' must be an array of at least two dimensions")
#   if (dims < 1L || dims > length(dn) - 1L) 
#     stop("invalid 'dims'")
#   n <- prod(dn[1L:dims])
#   dn <- dn[-(1L:dims)]
#   z <- if (is.complex(x)) 
#     .Internal(colMeans(Re(x), n, prod(dn), na.rm)) + (0+1i) * 
#     .Internal(colMeans(Im(x), n, prod(dn), na.rm))
#   else .Internal(colMeans(x, n, prod(dn), na.rm))
#   if (length(dn) > 1L) {
#     dim(z) <- dn
#     dimnames(z) <- dimnames(x)[-(1L:dims)]
#   }
#   else names(z) <- dimnames(x)[[dims + 1]]
#   z
# }
# <bytecode: 0x0000000008f89d20>
#   <environment: namespace:base>
Co? To również po prostu nazywa .Internal(colMeans(..., które możemy również znaleźć w króliczej nory. Czym to się różni od .Internal(lapply(..? W rzeczywistości szybki benchmark pokazuje, że sapply Działa nie gorzej niż colMeans i znacznie lepiej niż pętla for dla dużego zbioru danych
m <- as.data.frame(matrix(1:1e7, ncol = 1e5))
system.time(colMeans(m))
# user  system elapsed 
# 1.69    0.03    1.73 
system.time(sapply(m, mean))
# user  system elapsed 
# 1.50    0.03    1.60 
system.time(apply(m, 2, mean))
# user  system elapsed 
# 3.84    0.03    3.90 
system.time(for(i in 1:ncol(m)) mean(m[, i]))
# user  system elapsed 
# 13.78    0.01   13.93 

Innymi słowy, czy poprawne jest stwierdzenie, że lapply i vapply są faktycznie wektoryzowane (w porównaniu do apply, która jest pętlą for, która również telefony lapply) i co tak naprawdę chciał powiedzieć Patrick Burns?

Author: Jared Burrows, 2015-03-11

4 answers

Po pierwsze, w twoim przykładzie wykonujesz testy na " danych.frame", co nie jest sprawiedliwe dla colMeans, apply i "[.data.frame" ponieważ mają nad głową:

system.time(as.matrix(m))  #called by `colMeans` and `apply`
#   user  system elapsed 
#   1.03    0.00    1.05
system.time(for(i in 1:ncol(m)) m[, i])  #in the `for` loop
#   user  system elapsed 
#  12.93    0.01   13.07

Na matrycy obraz jest nieco inny:

mm = as.matrix(m)
system.time(colMeans(mm))
#   user  system elapsed 
#   0.01    0.00    0.01 
system.time(apply(mm, 2, mean))
#   user  system elapsed 
#   1.48    0.03    1.53 
system.time(for(i in 1:ncol(mm)) mean(mm[, i]))
#   user  system elapsed 
#   1.22    0.00    1.21

Regading głównej części pytania, głównej różnicy między lapply/mapply/etc i proste pętle R to miejsce, w którym odbywa się pętla. Jak zauważa Roland, obie pętle C i R muszą ocenić funkcję R w każdej iteracji, która jest najbardziej kosztowna. The really szybkie funkcje C to te, które robią wszystko w C, więc chyba o to chodzi?

Przykład, w którym znajdujemy średnią w każdym z elementów "listy":

(EDIT May 11 '16: uważam, że przykład ze znalezieniem "średniej" nie jest dobrym ustawieniem dla różnic między iteracyjnie ocenianiem funkcji R A skompilowanym kodem, (1) ze względu na specyfikę algorytmu średniej R na "numerycznej"s nad prostą sum(x) / length(x) i (2) powinien bardziej sensowne jest testowanie na"liście" z length(x) >> lengths(x). Tak więc przykład" mean " zostaje przeniesiony na koniec i zastąpiony innym.)

Jako prosty przykład możemy rozważyć znalezienie przeciwieństwa każdego length == 1 elementu "listy":

W pliku tmp.c:

#include <R.h>
#define USE_RINTERNALS 
#include <Rinternals.h>
#include <Rdefines.h>

/* call a C function inside another */
double oppC(double x) { return(ISNAN(x) ? NA_REAL : -x); }
SEXP sapply_oppC(SEXP x)
{
    SEXP ans = PROTECT(allocVector(REALSXP, LENGTH(x)));
    for(int i = 0; i < LENGTH(x); i++) 
        REAL(ans)[i] = oppC(REAL(VECTOR_ELT(x, i))[0]);

    UNPROTECT(1);
    return(ans);
}

/* call an R function inside a C function;
 * will be used with 'f' as a closure and as a builtin */    
SEXP sapply_oppR(SEXP x, SEXP f)
{
    SEXP call = PROTECT(allocVector(LANGSXP, 2));
    SETCAR(call, install(CHAR(STRING_ELT(f, 0))));

    SEXP ans = PROTECT(allocVector(REALSXP, LENGTH(x)));     
    for(int i = 0; i < LENGTH(x); i++) { 
        SETCADR(call, VECTOR_ELT(x, i));
        REAL(ans)[i] = REAL(eval(call, R_GlobalEnv))[0];
    }

    UNPROTECT(2);
    return(ans);
}

I na stronie R:

system("R CMD SHLIB /home/~/tmp.c")
dyn.load("/home/~/tmp.so")

Z danymi:

set.seed(007)
myls = rep_len(as.list(c(NA, runif(3))), 1e7)

#a closure wrapper of `-`
oppR = function(x) -x

for_oppR = compiler::cmpfun(function(x, f)
{
    f = match.fun(f)  
    ans = numeric(length(x))
    for(i in seq_along(x)) ans[[i]] = f(x[[i]])
    return(ans)
})

Benchmarking:

#call a C function iteratively
system.time({ sapplyC =  .Call("sapply_oppC", myls) }) 
#   user  system elapsed 
#  0.048   0.000   0.047 

#evaluate an R closure iteratively
system.time({ sapplyRC =  .Call("sapply_oppR", myls, "oppR") }) 
#   user  system elapsed 
#  3.348   0.000   3.358 

#evaluate an R builtin iteratively
system.time({ sapplyRCprim =  .Call("sapply_oppR", myls, "-") }) 
#   user  system elapsed 
#  0.652   0.000   0.653 

#loop with a R closure
system.time({ forR = for_oppR(myls, "oppR") })
#   user  system elapsed 
#  4.396   0.000   4.409 

#loop with an R builtin
system.time({ forRprim = for_oppR(myls, "-") })
#   user  system elapsed 
#  1.908   0.000   1.913 

#for reference and testing 
system.time({ sapplyR = unlist(lapply(myls, oppR)) })
#   user  system elapsed 
#  7.080   0.068   7.170 
system.time({ sapplyRprim = unlist(lapply(myls, `-`)) }) 
#   user  system elapsed 
#  3.524   0.064   3.598 

all.equal(sapplyR, sapplyRprim)
#[1] TRUE 
all.equal(sapplyR, sapplyC)
#[1] TRUE
all.equal(sapplyR, sapplyRC)
#[1] TRUE
all.equal(sapplyR, sapplyRCprim)
#[1] TRUE
all.equal(sapplyR, forR)
#[1] TRUE
all.equal(sapplyR, forRprim)
#[1] TRUE

(podąża za oryginalnym przykładem znalezienia średniej):

#all computations in C
all_C = inline::cfunction(sig = c(R_ls = "list"), body = '
    SEXP tmp, ans;
    PROTECT(ans = allocVector(REALSXP, LENGTH(R_ls)));

    double *ptmp, *pans = REAL(ans);

    for(int i = 0; i < LENGTH(R_ls); i++) {
        pans[i] = 0.0;

        PROTECT(tmp = coerceVector(VECTOR_ELT(R_ls, i), REALSXP));
        ptmp = REAL(tmp);

        for(int j = 0; j < LENGTH(tmp); j++) pans[i] += ptmp[j];

        pans[i] /= LENGTH(tmp);

        UNPROTECT(1);
    }

    UNPROTECT(1);
    return(ans);
')

#a very simple `lapply(x, mean)`
C_and_R = inline::cfunction(sig = c(R_ls = "list"), body = '
    SEXP call, ans, ret;

    PROTECT(call = allocList(2));
    SET_TYPEOF(call, LANGSXP);
    SETCAR(call, install("mean"));

    PROTECT(ans = allocVector(VECSXP, LENGTH(R_ls)));
    PROTECT(ret = allocVector(REALSXP, LENGTH(ans)));

    for(int i = 0; i < LENGTH(R_ls); i++) {
        SETCADR(call, VECTOR_ELT(R_ls, i));
        SET_VECTOR_ELT(ans, i, eval(call, R_GlobalEnv));
    }

    double *pret = REAL(ret);
    for(int i = 0; i < LENGTH(ans); i++) pret[i] = REAL(VECTOR_ELT(ans, i))[0];

    UNPROTECT(3);
    return(ret);
')                    

R_lapply = function(x) unlist(lapply(x, mean))                       

R_loop = function(x) 
{
    ans = numeric(length(x))
    for(i in seq_along(x)) ans[i] = mean(x[[i]])
    return(ans)
} 

R_loopcmp = compiler::cmpfun(R_loop)


set.seed(007); myls = replicate(1e4, runif(1e3), simplify = FALSE)
all.equal(all_C(myls), C_and_R(myls))
#[1] TRUE
all.equal(all_C(myls), R_lapply(myls))
#[1] TRUE
all.equal(all_C(myls), R_loop(myls))
#[1] TRUE
all.equal(all_C(myls), R_loopcmp(myls))
#[1] TRUE

microbenchmark::microbenchmark(all_C(myls), 
                               C_and_R(myls), 
                               R_lapply(myls), 
                               R_loop(myls), 
                               R_loopcmp(myls), 
                               times = 15)
#Unit: milliseconds
#            expr       min        lq    median        uq      max neval
#     all_C(myls)  37.29183  38.19107  38.69359  39.58083  41.3861    15
#   C_and_R(myls) 117.21457 123.22044 124.58148 130.85513 169.6822    15
#  R_lapply(myls)  98.48009 103.80717 106.55519 109.54890 116.3150    15
#    R_loop(myls) 122.40367 130.85061 132.61378 138.53664 178.5128    15
# R_loopcmp(myls) 105.63228 111.38340 112.16781 115.68909 128.1976    15
 69
Author: alexis_laz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-11 14:26:41

Dla mnie wektoryzacja polega przede wszystkim na tym, aby Twój kod był łatwiejszy do napisania i łatwiejszy do zrozumienia.

Celem wektoryzowanej funkcji jest wyeliminowanie księgowości związanej z pętlą for. Na przykład zamiast:

means <- numeric(length(mtcars))
for (i in seq_along(mtcars)) {
  means[i] <- mean(mtcars[[i]])
}
sds <- numeric(length(mtcars))
for (i in seq_along(mtcars)) {
  sds[i] <- sd(mtcars[[i]])
}

Możesz napisać:

means <- vapply(mtcars, mean, numeric(1))
sds   <- vapply(mtcars, sd, numeric(1))

To ułatwia sprawdzenie, co jest takie samo (dane wejściowe), a co inne (funkcja, którą stosujesz).

Dodatkową zaletą wektoryzacji jest to, że pętla for jest często zapisywana w C, raczej niż w R. ma to istotne zalety, ale nie sądzę, że jest to kluczowa właściwość wektoryzacji. Wektoryzacja polega zasadniczo na ratowaniu mózgu, a nie na ratowaniu pracy komputera.

 61
Author: hadley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-11 12:20:51

Zgadzam się z poglądem Patricka Burnsa, że jest to raczej ukrywanie pętli , a nie wektoryzacja kodu . Oto dlaczego:

Rozważ ten C fragment kodu:

for (int i=0; i<n; i++)
  c[i] = a[i] + b[i]

to, co chcielibyśmy zrobić, jest dość jasne. Ale to, jak zadanie jest wykonywane lub jak może być wykonane, nie jest tak naprawdę. A for-loop domyślnie jest konstrukcją szeregową. Nie informuje, czy i w jaki sposób można robić rzeczy równolegle.

Najbardziej oczywistym sposobem jest to, że kod jest uruchamiany w sposób sekwencyjny. Wczytaj a[i] i b[i] do rejestrów, dodaj je, zapisz wynik w c[i] i zrób to dla każdego i.

[[30]}jednak nowoczesne procesory mają wektor lub SIMD zbiór instrukcji, który może działać nawektor danych podczas tej samej instrukcji podczas wykonywania tej samej operacji (np. dodanie dwóch wektorów, jak pokazano powyżej). W zależności od procesora/architektury może możliwe jest dodawanie, powiedzmy, czterech liczb z a i b pod tą samą instrukcją, zamiast pojedynczo.
[[30]} chcielibyśmy wykorzystać pojedyncza Instrukcja wiele danych i wykonać równoległość poziomu danych, np. załadować 4 rzeczy na raz, dodać 4 rzeczy na raz, zapisać 4 rzeczy na raz, na przykład. A to jest wektoryzacja kodu .

Zauważ, że różni się to od paralelizacji kodu - gdzie wiele obliczenia są wykonywane jednocześnie.

Byłoby wspaniale, gdyby kompilator zidentyfikował takie bloki kodu i automatycznie wektoryzował je, co jest trudnym zadaniem. Automatyczna wektoryzacja kodu to trudny temat badawczy w informatyce. Ale z czasem Kompilatory stały się w tym lepsze. Możesz sprawdzić możliwości auto wektoryzacji GNU-gcc tutaj . Podobnie dla LLVM-clang tutaj . I można również znajdź kilka benchmarków w ostatnim linku w porównaniu z gcc i ICC (kompilator Intel C++).

gcc (jestem na v4.9) na przykład nie wektoryzuje kodu automatycznie przy optymalizacji poziomu -O2. Więc jeśli wykonamy kod pokazany powyżej, będzie on uruchamiany sekwencyjnie. Oto Czas dodania dwóch wektorów całkowitych o długości 500 milionów.

Musimy dodać flagę -ftree-vectorize lub zmienić optymalizację na poziom -O3. (Zauważ, że -O3 wykonuje inne dodatkowe optymalizacje również). Flaga -fopt-info-vec jest przydatna, ponieważ informuje, kiedy pętla została pomyślnie wektoryzowana).

# compiling with -O2, -ftree-vectorize and  -fopt-info-vec
# test.c:32:5: note: loop vectorized
# test.c:32:5: note: loop versioned for vectorization because of possible aliasing
# test.c:32:5: note: loop peeled for vectorization to enhance alignment    

To mówi nam, że funkcja jest wektoryzowana. Poniżej przedstawiono timingi porównujące zarówno wersje bez wektoryzowane, jak i wektoryzowane na wektorach całkowitych o długości 500 milionów:

x = sample(100L, 500e6L, TRUE)
y = sample(100L, 500e6L, TRUE)
z = vector("integer", 500e6L) # result vector

# non-vectorised, -O2
system.time(.Call("Csum", x, y, z))
#    user  system elapsed 
#   1.830   0.009   1.852

# vectorised using flags shown above at -O2
system.time(.Call("Csum", x, y, z))
#    user  system elapsed 
#   0.361   0.001   0.362

# both results are checked for identicalness, returns TRUE

Tę część można bezpiecznie pominąć bez utraty ciągłości.

Kompilatory nie zawsze będą miały wystarczające informacje do wektoryzacji. Moglibyśmy użyć Specyfikacja OpenMP do programowania równoległego , która również dostarcza dyrektywę kompilatora simd do instruowania kompilatorów do wektoryzacji kodu. Ważne jest, aby upewnić się, że nie ma nakładania się pamięci, warunków wyścigu itp.. podczas ręcznego wektoryzowania kodu, w przeciwnym razie spowoduje to nieprawidłowe wyniki.

#pragma omp simd
for (i=0; i<n; i++) 
  c[i] = a[i] + b[i]

Robiąc to, zwracamy się do kompilatora o wektoryzację bez względu na wszystko. Będziemy musieli aktywować rozszerzenia OpenMP używając znacznika czasu kompilacji -fopenmp. Robiąc to:

# timing with -O2 + OpenMP with simd
x = sample(100L, 500e6L, TRUE)
y = sample(100L, 500e6L, TRUE)
z = vector("integer", 500e6L) # result vector
system.time(.Call("Cvecsum", x, y, z))
#    user  system elapsed 
#   0.360   0.001   0.360
Co jest świetne! Zostało to przetestowane z gcc v6.2.0 i llvm clang v3.9. 0 (oba zainstalowane przez homebrew, MacOS 10.12.3), oba obsługują OpenMP 4.0.

W tym sensie, nawet jeśli Strona Wikipedii dotycząca programowania tablic wspomina, że języki operujące na całych tablicach zwykle nazywają to operacjamiwektoryzowanymi , to tak naprawdę jest toukrywanie pętli IMO (chyba że jest to faktycznie wektoryzowane).

W przypadku R, nawet rowSums() lub colSums() kod w C nie wykorzystuje kod wektoryzacji IUC; jest to tylko pętla w C. to samo dotyczy lapply(). W przypadku apply(), jest w R. wszystkie z nich są zatem ukrywaniem pętli.

W skrócie, zawijanie funkcji R przez:

Wystarczy napisać for-loop W C!= wektoryzacja kodu.
wystarczy napisać for-loop w R!= wektoryzacja kodu.

Intel Math Kernel Library (MKL) na przykład implementuje wektoryzowane formy funkcji.

HTH


Bibliografia:

  1. W 2007 roku, w ramach programu "Horyzont 2020", w ramach programu "Horyzont 2020", w ramach programu "Horyzont 2020" - programu ramowego w zakresie badań naukowych i innowacji (2014-2020), zrealizowano]}
 45
Author: Arun,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-22 22:44:02

Więc sumując świetne odpowiedzi/komentarze do jakiejś ogólnej odpowiedzi i podaj trochę tła: R ma 4 rodzaje pętli (W porządku nie-wektoryzowanym do wektoryzowanego )

  1. R for pętla, która wielokrotnie wywołuje funkcje R w każdej iteracji (Nie wektoryzowana )
  2. Pętla C, która wielokrotnie wywołuje funkcje R w każdej iteracji (Nie wektoryzowana )
  3. pętla C wywołująca funkcję r tylko raz (nieco wektoryzowana )
  4. A prosta pętla C, która nie wywołuje żadnej r funkcji w ogóle i używa jej własnych skompilowanych funkcji (Wektoryzowanych)

Rodzina *apply jest więc drugim rodzajem. Z wyjątkiem apply, który jest bardziej pierwszego typu

Możesz to zrozumieć z komentarza w jego kodzie źródłowym

/* .Internal(lapply (X, FUN)) */

/* to jest coś specjalnego .Wewnętrzne, tak samo bezcenne argumenty. Jest to
wywołany z zamknięcia wrapper, więc X i FUN to obietnice. Zabawa musi być bezcenne do wykorzystania np. w bquote . */

Oznacza to, że lapplykod S C akceptuje nieocenioną funkcję z R, a następnie ocenia ją w samym kodzie C. Jest to w zasadzie różnica pomiędzy lapplys .Internal wywołaniem

.Internal(lapply(X, FUN))

Który ma FUN argument, który posiada funkcję R

I colMeans .Internal wywołanie, które nie ma } argumentu FUN

.Internal(colMeans(Re(x), n, prod(dn), na.rm))

colMeans, W przeciwieństwie do lapply wie dokładnie jakiej funkcji potrzebuje użyć, dlatego oblicza średnią wewnątrz kodu C.

Można wyraźnie zobaczyć proces oceny funkcji R w każdej iteracji wewnątrz lapply kod C

 for(R_xlen_t i = 0; i < n; i++) {
      if (realIndx) REAL(ind)[0] = (double)(i + 1);
      else INTEGER(ind)[0] = (int)(i + 1);
      tmp = eval(R_fcall, rho);   // <----------------------------- here it is
      if (MAYBE_REFERENCED(tmp)) tmp = lazy_duplicate(tmp);
      SET_VECTOR_ELT(ans, i, tmp);
   }

Podsumowując wszystko, lapply nie jest wektoryzowana, choć ma dwie możliwe przewagi nad zwykłą pętlą R for

  1. Dostęp i przypisywanie w pętli wydaje się być szybsze w C (tj. w lapply ing a funkcja) chociaż różnica wydaje się duża, to jednak pozostajemy na poziomie mikrosekundy, a kosztowną rzeczą jest wycena funkcji R w każdej iteracji. Prosty przykład:

    ffR = function(x)  {
        ans = vector("list", length(x))
        for(i in seq_along(x)) ans[[i]] = x[[i]]
        ans 
    }
    
    ffC = inline::cfunction(sig = c(R_x = "data.frame"), body = '
        SEXP ans;
        PROTECT(ans = allocVector(VECSXP, LENGTH(R_x)));
        for(int i = 0; i < LENGTH(R_x); i++) 
               SET_VECTOR_ELT(ans, i, VECTOR_ELT(R_x, i));
        UNPROTECT(1);
        return(ans); 
    ')
    
    set.seed(007) 
    myls = replicate(1e3, runif(1e3), simplify = FALSE)     
    mydf = as.data.frame(myls)
    
    all.equal(ffR(myls), ffC(myls))
    #[1] TRUE 
    all.equal(ffR(mydf), ffC(mydf))
    #[1] TRUE
    
    microbenchmark::microbenchmark(ffR(myls), ffC(myls), 
                                   ffR(mydf), ffC(mydf),
                                   times = 30)
    #Unit: microseconds
    #      expr       min        lq    median        uq       max neval
    # ffR(myls)  3933.764  3975.076  4073.540  5121.045 32956.580    30
    # ffC(myls)    12.553    12.934    16.695    18.210    19.481    30
    # ffR(mydf) 14799.340 15095.677 15661.889 16129.689 18439.908    30
    # ffC(mydf)    12.599    13.068    15.835    18.402    20.509    30
    
  2. Jak wspomniano przez @ Roland, uruchamia skompilowaną pętlę C, a raczej zinterpretowaną pętlę R


Chociaż podczas wektoryzacji kodu, są pewne rzeczy, które musisz wziąć pod uwagę.

  1. Jeśli twój zbiór danych (nazwijmy go df) należy do klasy data.frame, niektóre funkcje wektorowe (takie jakcolMeans, colSums, rowSums, itd.) będzie musiał najpierw przekształcić go w matrycę, po prostu dlatego, że tak zostały zaprojektowane. Oznacza to, że dla dużego df może to spowodować ogromne obciążenie. Chociaż lapply nie będzie musiał tego robić, ponieważ wyciąga rzeczywiste wektory z df (ponieważ data.frame jest tylko listą wektorów) i dlatego, jeśli masz nie tyle kolumn, ale wiele wierszy, lapply(df, mean) może być czasami lepszą opcją niż colMeans(df).
  2. kolejną rzeczą do zapamiętania jest to, że R ma duża różnorodność różnych typów funkcji, takich jak .Primitive, oraz generyczne (S3, S4) zobacz tutaj aby uzyskać dodatkowe informacje. Funkcja generyczna musi wykonać wysyłkę metody, która czasami jest kosztowną operacją. Na przykład, mean jest ogólną funkcją S3, podczas gdy sum jest Primitive. Tak więc czasami lapply(df, sum) może być bardzo wydajne w porównaniu colSums z powodów wymienionych powyżej
 35
Author: David Arenburg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-09 22:36:56