Функции группировки (tapply, by, aggregate) и семейство * apply

всякий раз, когда я хочу сделать что-то "map"py в R, я обычно пытаюсь использовать функцию в apply семья.

однако я никогда до конца не понимал различий между ними-как {sapply, lapply, etc.} примените функцию к входу / сгруппированному входу, как будет выглядеть выход или даже каким может быть вход-поэтому я часто просто просматриваю их все, пока не получу то, что хочу.

может кто-нибудь объяснить, как использовать, какой, когда?

мой текущее (возможно, неправильное/неполное) понимание...

  1. sapply(vec, f): входной сигнал является вектором. выход-это вектор / Матрица, где element i is f(vec[i]), давая вам матрицу, если f имеет многоэлементный выход

  2. lapply(vec, f): как sapply, но вывод список?

  3. apply(matrix, 1/2, f): вход является матрицей. выход-это вектор, где element i является f (строка / col I матрицы)
  4. tapply(vector, grouping, f): выход-это матрица / массив, где элементом матрицы / массива является значение f в группу g вектор, а g получает толкнул в строке / col имена
  5. by(dataframe, grouping, f): да g быть группировка. применить f для каждого столбца группы/Таблицы данных. довольно распечатать группировку и значение f в каждом столбце.
  6. aggregate(matrix, grouping, f): аналогично by, но вместо того, чтобы довольно печатать вывод, aggregate вставляет все в фрейм данных.

боковой вопрос: я до сих пор не изучил plyr или reshape -- would plyr или reshape заменить все это полностью?

9 ответов


R имеет много * применить функции, которые умело описаны в файлах справки (например,?apply). Однако их достаточно, чтобы начинающим пользователям было трудно решить, какой из них подходит для их ситуации, или даже запомнить их все. У них может быть общее ощущение ,что" я должен использовать функцию *apply здесь", но сначала может быть трудно держать их все прямо.

несмотря на то, что (отмечено в других ответах) большая часть функциональности * применить семья охвачена чрезвычайно популярным plyr пакет, базовые функции остаются полезными и стоит знать.

этот ответ призван действовать как своего рода указатель для новых пользователей, чтобы помочь направить их к правильной * применить функцию для их конкретной проблемы. Обратите внимание, это не предназначен для простого отрыгивания или замены документации R! Надеемся, что этот ответ поможет вам решить ,какая функция * apply соответствует вашей ситуации и тогда это до вас, чтобы исследовать его дальше. За одним исключением различия в производительности не будут устранены.

  • применить - когда вы хотите применить функцию к строкам или столбцам матрицы (и многомерных аналогов); обычно не рекомендуется для фреймов данных, поскольку он будет принуждать к матрице в первую очередь.

    # Two dimensional matrix
    M <- matrix(seq(1,16), 4, 4)
    
    # apply min to rows
    apply(M, 1, min)
    [1] 1 2 3 4
    
    # apply max to columns
    apply(M, 2, max)
    [1]  4  8 12 16
    
    # 3 dimensional array
    M <- array( seq(32), dim = c(4,4,2))
    
    # Apply sum across each M[*, , ] - i.e Sum across 2nd and 3rd dimension
    apply(M, 1, sum)
    # Result is one-dimensional
    [1] 120 128 136 144
    
    # Apply sum across each M[*, *, ] - i.e Sum across 3rd dimension
    apply(M, c(1,2), sum)
    # Result is two-dimensional
         [,1] [,2] [,3] [,4]
    [1,]   18   26   34   42
    [2,]   20   28   36   44
    [3,]   22   30   38   46
    [4,]   24   32   40   48
    

    если вы хотите, чтобы строка / столбец означали или суммы для 2D-матрицы, обязательно расследовать высоко оптимизирован, молниеносноcolMeans, rowMeans, colSums, rowSums.

  • lapply - когда вы хотите применить функцию к каждому элементу список по очереди и получить список обратно.

    это рабочая лошадка многих других функций * apply. Корка их код и вы часто найдете lapply внизу.

    x <- list(a = 1, b = 1:3, c = 10:100) 
    lapply(x, FUN = length) 
    $a 
    [1] 1
    $b 
    [1] 3
    $c 
    [1] 91
    lapply(x, FUN = sum) 
    $a 
    [1] 1
    $b 
    [1] 6
    $c 
    [1] 5005
    
  • поставка - когда вы хотите применить функцию к каждому элементу список, в свою очередь, но вы хотите вектор назад, а не список.

    если вы обнаружите, что набираете unlist(lapply(...)), остановиться и рассмотреть sapply.

    x <- list(a = 1, b = 1:3, c = 10:100)
    # Compare with above; a named vector, not a list 
    sapply(x, FUN = length)  
    a  b  c   
    1  3 91
    
    sapply(x, FUN = sum)   
    a    b    c    
    1    6 5005 
    

    в более продвинутом использовании sapply он попытается принудить результат в многомерный массив, если это необходимо. Например, если наша функция возвращает векторы одинаковой длины,sapply будет использовать их в качестве столбцов матрицы:

    sapply(1:5,function(x) rnorm(3,x))
    

    если наша функция возвращает 2-мерную матрицу,sapply будет делать по существу то же самое, рассматривая каждую возвращенную матрицу как один длинный вектор:

    sapply(1:5,function(x) matrix(x,2,2))
    

    если мы не указать simplify = "array" в этом случае он будет использовать отдельных матриц для построения многомерного массива:

    sapply(1:5,function(x) matrix(x,2,2), simplify = "array")
    

    каждое из этих действий, конечно, зависит от нашей функции, возвращающей векторы или матрицы одинаковой длины или измерение.

  • vapply - когда вы хотите использовать sapply но, возможно, нужно выжмите из кода еще немного скорости.

    на vapply, вы в основном даете R пример того, какие вещи ваша функция вернется, что может сэкономить некоторое время принудительного возврата значения, соответствующие одному атомарному вектору.

    x <- list(a = 1, b = 1:3, c = 10:100)
    #Note that since the advantage here is mainly speed, this
    # example is only for illustration. We're telling R that
    # everything returned by length() should be an integer of 
    # length 1. 
    vapply(x, FUN = length, FUN.VALUE = 0L) 
    a  b  c  
    1  3 91
    
  • mapply - для Когда вы имеете несколько структуры данных (например, векторы, списки), и вы хотите применить функцию к 1-м элементам каждого, а затем 2-го элемента каждого и т. д., принуждая результат к вектору / массиву, как в sapply.

    это многомерно в том смысле, что ваша функция должна принимать несколько аргументов.

    #Sums the 1st elements, the 2nd elements, etc. 
    mapply(sum, 1:5, 1:5, 1:5) 
    [1]  3  6  9 12 15
    #To do rep(1,4), rep(2,3), etc.
    mapply(rep, 1:4, 4:1)   
    [[1]]
    [1] 1 1 1 1
    
    [[2]]
    [1] 2 2 2
    
    [[3]]
    [1] 3 3
    
    [[4]]
    [1] 4
    
  • карта - обертка для mapply С SIMPLIFY = FALSE, поэтому гарантируется возврат список.

    Map(sum, 1:5, 1:5, 1:5)
    [[1]]
    [1] 3
    
    [[2]]
    [1] 6
    
    [[3]]
    [1] 9
    
    [[4]]
    [1] 12
    
    [[5]]
    [1] 15
    
  • rapply - когда вы хотите применить функцию к каждому элементу вложенного списка структура, рекурсивно.

    чтобы дать вам некоторое представление о том, как редкость rapply есть, я забыл об этом, когда впервые разместил этот ответ! Очевидно, я уверен, что многие люди используют его, но YMMV. rapply лучше всего иллюстрируется пользовательской функцией для применить:

    # Append ! to string, otherwise increment
    myFun <- function(x){
        if(is.character(x)){
          return(paste(x,"!",sep=""))
        }
        else{
          return(x + 1)
        }
    }
    
    #A nested list structure
    l <- list(a = list(a1 = "Boo", b1 = 2, c1 = "Eeek"), 
              b = 3, c = "Yikes", 
              d = list(a2 = 1, b2 = list(a3 = "Hey", b3 = 5)))
    
    
    # Result is named vector, coerced to character          
    rapply(l, myFun)
    
    # Result is a nested list like l, with values altered
    rapply(l, myFun, how="replace")
    
  • tapply - когда вы хотите применить функцию к подмножеств в вектор и подмножества определяются некоторым другим вектором, обычно a факторный.

    паршивая овца * применить семьи, своего рода. Использование файла справки фраза "ragged array" может быть немного заблуждение, но это на самом деле совсем простой.

    A вектор:

    x <- 1:20
    

    фактор (такой же длины!) определение групп:

    y <- factor(rep(letters[1:5], each = 4))
    

    сложите значения в x внутри каждой подгруппы, определенной y:

    tapply(x, y, sum)  
     a  b  c  d  e  
    10 26 42 58 74 
    

    более сложные примеры могут быть обработаны, где определены подгруппы по уникальным сочетаниям перечислите несколько факторов. tapply is подобно по духу функциям split-apply-combine, которые общие в R (aggregate, by, ave, ddply, etc.) Следовательно свой статус паршивой овцы.


на боковой ноте, вот как различные plyr функции соответствуют базе *apply функции (из вступления в документ plyr с веб-страницы plyr http://had.co.nz/plyr/)

Base function   Input   Output   plyr function 
---------------------------------------
aggregate        d       d       ddply + colwise 
apply            a       a/l     aaply / alply 
by               d       l       dlply 
lapply           l       l       llply  
mapply           a       a/l     maply / mlply 
replicate        r       a/l     raply / rlply 
sapply           l       a       laply 

одна из целей plyr предоставляет согласованные соглашения об именах для каждой из функций, кодируя входные и выходные типы данных в имени функции. Он также обеспечивает согласованность в выводе, в этом выводе из dlply() легко проходимы для ldply() произвести полезный выход, etc.

концептуально, обучение plyr не сложнее, чем понять базу *apply функции.

plyr и reshape функции заменили почти все эти функции в моем ежедневном использовании. Но, также из вступления к документу Plyr:

соответствующие функции tapply и sweep не имеют соответствующей функции в plyr, и остаются полезными. merge полезно для объединения резюме с исходными данными.


из слайда 21 http://www.slideshare.net/hadley/plyr-one-data-analytic-strategy:

apply, sapply, lapply, by, aggregate

(надеюсь, это ясно, что apply соответствует @ и aggregate соответствует @ etc. Слайд 20 того же slideshare прояснит, если вы не получите его из этого изображения.)

(слева-вход, вверху-выход)


начинается с отличный ответ Джорана -- сомнительно, что что-нибудь может лучше этого.

тогда следующая мнемоника может помочь запомнить различия между ними. Хотя некоторые из них очевидны, другие могут быть менее очевидны-для них вы найдете оправдание в дискуссиях Джорана.

Мнемоника

  • lapply Это список применить, который действует от список или вектор и возвращает список.
  • sapply это простой lapply (функция по умолчанию возвращает вектор или матрицу, когда это возможно)
  • vapply - это проверил применить (позволяет заранее определить тип возвращаемого объекта)
  • rapply это рекурсивные применить для вложенных списков, т. е. списков в списках
  • tapply это тегом применить, где теги идентифицируют подмножества
  • apply is generic: применяет функцию к строкам или столбцам матрицы (или, в более общем плане, к размерам массива)

создание правильного фона

при использовании apply семья все еще чувствует себя немного чуждым для вас, то это может быть, что вы не хватает ключевой точки зрения.

эти две статьи могут помочь. Они обеспечивают необходимую основу для мотивации методы функционального программирования что предоставляются apply семейство функций.

пользователи Lisp сразу распознают парадигму. Если вы не знакомы с Lisp, как только вы получите свою голову вокруг FP, вы получите мощную точку зрения для использования в R - и apply будет иметь гораздо больше смысла.


так как я понял, что (очень отличные) ответы на этот пост не хватает by и aggregate объяснения. Вот мой вклад.

BY

на by функция, как указано в документации, может быть, хотя, как "обертка" для tapply. Сила by возникает, когда мы хотим вычислить задача, которая tapply не могу справиться. Одним из примеров является этот код:

ct <- tapply(iris$Sepal.Width , iris$Species , summary )
cb <- by(iris$Sepal.Width , iris$Species , summary )

 cb
iris$Species: setosa
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.300   3.200   3.400   3.428   3.675   4.400 
-------------------------------------------------------------- 
iris$Species: versicolor
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.000   2.525   2.800   2.770   3.000   3.400 
-------------------------------------------------------------- 
iris$Species: virginica
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.200   2.800   3.000   2.974   3.175   3.800 


ct
$setosa
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.300   3.200   3.400   3.428   3.675   4.400 

$versicolor
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.000   2.525   2.800   2.770   3.000   3.400 

$virginica
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.200   2.800   3.000   2.974   3.175   3.800 

если мы напечатаем эти два объекта,ct и cb, мы "по существу" имеют те же результаты, и единственные различия заключаются в том, как они показаны и разные class атрибуты, соответственно by на cb и array на ct.

как я уже сказал, Сила by возникает, когда мы не можем использовать tapply; следующий код, пример:

 tapply(iris, iris$Species, summary )
Error in tapply(iris, iris$Species, summary) : 
  arguments must have same length

R говорит, что аргументы должны иметь одинаковую длину, скажем :" мы хотим вычислить summary всех переменных в iris по фактору Species": но R просто не может этого сделать, потому что он не знает, как обращаться.

С by функция R отправка определенного метода для data frame класс, а затем пусть summary функция работает, даже если длина первого аргумента (и тип тоже) различны.

bywork <- by(iris, iris$Species, summary )

bywork
iris$Species: setosa
  Sepal.Length    Sepal.Width     Petal.Length    Petal.Width          Species  
 Min.   :4.300   Min.   :2.300   Min.   :1.000   Min.   :0.100   setosa    :50  
 1st Qu.:4.800   1st Qu.:3.200   1st Qu.:1.400   1st Qu.:0.200   versicolor: 0  
 Median :5.000   Median :3.400   Median :1.500   Median :0.200   virginica : 0  
 Mean   :5.006   Mean   :3.428   Mean   :1.462   Mean   :0.246                  
 3rd Qu.:5.200   3rd Qu.:3.675   3rd Qu.:1.575   3rd Qu.:0.300                  
 Max.   :5.800   Max.   :4.400   Max.   :1.900   Max.   :0.600                  
-------------------------------------------------------------- 
iris$Species: versicolor
  Sepal.Length    Sepal.Width     Petal.Length   Petal.Width          Species  
 Min.   :4.900   Min.   :2.000   Min.   :3.00   Min.   :1.000   setosa    : 0  
 1st Qu.:5.600   1st Qu.:2.525   1st Qu.:4.00   1st Qu.:1.200   versicolor:50  
 Median :5.900   Median :2.800   Median :4.35   Median :1.300   virginica : 0  
 Mean   :5.936   Mean   :2.770   Mean   :4.26   Mean   :1.326                  
 3rd Qu.:6.300   3rd Qu.:3.000   3rd Qu.:4.60   3rd Qu.:1.500                  
 Max.   :7.000   Max.   :3.400   Max.   :5.10   Max.   :1.800                  
-------------------------------------------------------------- 
iris$Species: virginica
  Sepal.Length    Sepal.Width     Petal.Length    Petal.Width          Species  
 Min.   :4.900   Min.   :2.200   Min.   :4.500   Min.   :1.400   setosa    : 0  
 1st Qu.:6.225   1st Qu.:2.800   1st Qu.:5.100   1st Qu.:1.800   versicolor: 0  
 Median :6.500   Median :3.000   Median :5.550   Median :2.000   virginica :50  
 Mean   :6.588   Mean   :2.974   Mean   :5.552   Mean   :2.026                  
 3rd Qu.:6.900   3rd Qu.:3.175   3rd Qu.:5.875   3rd Qu.:2.300                  
 Max.   :7.900   Max.   :3.800   Max.   :6.900   Max.   :2.500     

это действительно работает и результат очень удивит. Это объект класса by что вдоль Species (скажем, для каждого из них) вычисляет summary каждого переменная.

обратите внимание, что если первый аргумент является data frame, отправленная функция должна иметь метод для этого класса объектов. Например, мы используем этот код mean функция у нас будет этот код, который вообще не имеет смысла:

 by(iris, iris$Species, mean)
iris$Species: setosa
[1] NA
------------------------------------------- 
iris$Species: versicolor
[1] NA
------------------------------------------- 
iris$Species: virginica
[1] NA
Warning messages:
1: In mean.default(data[x, , drop = FALSE], ...) :
  argument is not numeric or logical: returning NA
2: In mean.default(data[x, , drop = FALSE], ...) :
  argument is not numeric or logical: returning NA
3: In mean.default(data[x, , drop = FALSE], ...) :
  argument is not numeric or logical: returning NA

совокупность

aggregate можно рассматривать как другой другой способ использования tapply если мы используем его таким образом.

at <- tapply(iris$Sepal.Length , iris$Species , mean)
ag <- aggregate(iris$Sepal.Length , list(iris$Species), mean)

 at
    setosa versicolor  virginica 
     5.006      5.936      6.588 
 ag
     Group.1     x
1     setosa 5.006
2 versicolor 5.936
3  virginica 6.588

два различия заключаются в том, что второй аргумент aggregate должны список пока tapply can (не обязательно) быть списком и что вывод aggregate - это фрейм данных, в то время как один из tapply это array.

власть aggregate заключается в том, что он может легко обрабатывать подмножества данных с помощью subset аргумент и что у него есть методы для ts объекты и formula как хорошо.

эти элементы делают aggregate легче работать с этим tapply в некоторых ситуациях. Здесь некоторые примеры (доступны в документации):

ag <- aggregate(len ~ ., data = ToothGrowth, mean)

 ag
  supp dose   len
1   OJ  0.5 13.23
2   VC  0.5  7.98
3   OJ  1.0 22.70
4   VC  1.0 16.77
5   OJ  2.0 26.06
6   VC  2.0 26.14

мы можем добиться того же с tapply но синтаксис немного сложнее, а вывод (в некоторых случаях) менее читаемый:

att <- tapply(ToothGrowth$len, list(ToothGrowth$dose, ToothGrowth$supp), mean)

 att
       OJ    VC
0.5 13.23  7.98
1   22.70 16.77
2   26.06 26.14

есть и другие случаи, когда мы не можем использовать by или tapply и мы должны использовать aggregate.

 ag1 <- aggregate(cbind(Ozone, Temp) ~ Month, data = airquality, mean)

 ag1
  Month    Ozone     Temp
1     5 23.61538 66.73077
2     6 29.44444 78.22222
3     7 59.11538 83.88462
4     8 59.96154 83.96154
5     9 31.44828 76.89655

мы не можем получить предыдущий результат с tapply в одном вызове, но мы должны рассчитать среднее значение вдоль Month для каждого элемента, а затем объедините их (также обратите внимание, что мы должны вызвать na.rm = TRUE, потому что formula методы aggregate функция имеет по умолчанию na.action = na.omit):

ta1 <- tapply(airquality$Ozone, airquality$Month, mean, na.rm = TRUE)
ta2 <- tapply(airquality$Temp, airquality$Month, mean, na.rm = TRUE)

 cbind(ta1, ta2)
       ta1      ta2
5 23.61538 65.54839
6 29.44444 79.10000
7 59.11538 83.90323
8 59.96154 83.96774
9 31.44828 76.90000

С by мы просто не можем добиться того, что на самом деле следующий вызов функции возвращает ошибку (но скорее всего это связано с предоставленной функции, mean):

by(airquality[c("Ozone", "Temp")], airquality$Month, mean, na.rm = TRUE)

в других случаях результаты одинаковы, и различия только в классе (а затем, как он показан / напечатан, а не только -- пример, как его подмножество) object:

byagg <- by(airquality[c("Ozone", "Temp")], airquality$Month, summary)
aggagg <- aggregate(cbind(Ozone, Temp) ~ Month, data = airquality, summary)

предыдущий код достигает той же цели и результатов, в некоторые моменты Какой инструмент использовать просто вопрос личных вкусов и потребностей; предыдущие два объекта имеют очень разные потребности с точки зрения подстановки.


есть много отличных ответов, которые обсуждают различия в вариантах использования для каждой функции. Ни один из ответов не обсуждает различия в производительности. Это разумная причина, по которой различные функции ожидают различных входных данных и производят различные выходные данные, однако большинство из них имеют общую цель для оценки по рядам/группам. Мой ответ будет сосредоточен на производительности. Из-за выше создание входных данных из векторов включено в сроки, а также


это, возможно, стоит упомянуть ave. ave и tapply'ы или брат двоюродный. Он возвращает результаты в форме, которую вы можете подключить обратно в фрейм данных.

dfr <- data.frame(a=1:20, f=rep(LETTERS[1:5], each=4))
means <- tapply(dfr$a, dfr$f, mean)
##  A    B    C    D    E 
## 2.5  6.5 10.5 14.5 18.5 

## great, but putting it back in the data frame is another line:

dfr$m <- means[dfr$f]

dfr$m2 <- ave(dfr$a, dfr$f, FUN=mean) # NB argument name FUN is needed!
dfr
##   a f    m   m2
##   1 A  2.5  2.5
##   2 A  2.5  2.5
##   3 A  2.5  2.5
##   4 A  2.5  2.5
##   5 B  6.5  6.5
##   6 B  6.5  6.5
##   7 B  6.5  6.5
##   ...

в базовом пакете нет ничего, что работает как ave для целых фреймов данных (as by как tapply для кадров данных). Но вы можете подделать его:

dfr$foo <- ave(1:nrow(dfr), dfr$f, FUN=function(x) {
    x <- dfr[x,]
    sum(x$m*x$m2)
})
dfr
##     a f    m   m2    foo
## 1   1 A  2.5  2.5    25
## 2   2 A  2.5  2.5    25
## 3   3 A  2.5  2.5    25
## ...

несмотря на все отличные ответы здесь, есть еще 2 базовые функции, которые заслуживают упоминания, полезные outer функция и неясное eapply функции

внешний

outer очень полезная функция, скрытая как более мирская. Если Вы читаете справку для outer в его описании сказано:

The outer product of the arrays X and Y is the array A with dimension  
c(dim(X), dim(Y)) where element A[c(arrayindex.x, arrayindex.y)] =   
FUN(X[arrayindex.x], Y[arrayindex.y], ...).

что заставляет думать, что это полезно только для вещей типа линейной алгебры. Однако, его можно использовать много как mapply применить функцию к двум векторам входных данных. Разница в том, что mapply применит функцию к первым двум элементам, а затем ко вторым двум и т. д., Тогда как outer будет применять функцию к каждой комбинации одного элемента из первого вектора и одного из второго. Например:

 A<-c(1,3,5,7,9)
 B<-c(0,3,6,9,12)

mapply(FUN=pmax, A, B)

> mapply(FUN=pmax, A, B)
[1]  1  3  6  9 12

outer(A,B, pmax)

 > outer(A,B, pmax)
      [,1] [,2] [,3] [,4] [,5]
 [1,]    1    3    6    9   12
 [2,]    3    3    6    9   12
 [3,]    5    5    6    9   12
 [4,]    7    7    7    9   12
 [5,]    9    9    9    9   12

я лично использовал это, когда у меня есть вектор значений и вектор условий и хочу видеть, какие значения соответствуют которым условия.

eapply

eapply как lapply за исключением того, что вместо применения функции к каждому элементу списка, он применяет функцию к каждому элементу в среде. Например, если вы хотите найти список пользовательских функций в глобальной среде:

A<-c(1,3,5,7,9)
B<-c(0,3,6,9,12)
C<-list(x=1, y=2)
D<-function(x){x+1}

> eapply(.GlobalEnv, is.function)
$A
[1] FALSE

$B
[1] FALSE

$C
[1] FALSE

$D
[1] TRUE 

честно говоря, я не использую это очень много, но если вы создаете много пакетов или создаете много сред, это может пригодиться.


недавно я обнаружил довольно полезный sweep функция и добавьте ее сюда для полноты:

развертки

основная идея состоит в том, чтобы развертки через строку или столбец массива и возвращает измененный массив. Пример сделает это понятным (источник:datacamp):

предположим, у вас есть матрица и вы хотите стандартизация столбец-мудрый это:

dataPoints <- matrix(4:15, nrow = 4)

# Find means per column with `apply()`
dataPoints_means <- apply(dataPoints, 2, mean)

# Find standard deviation with `apply()`
dataPoints_sdev <- apply(dataPoints, 2, sd)

# Center the points 
dataPoints_Trans1 <- sweep(dataPoints, 2, dataPoints_means,"-")
print(dataPoints_Trans1)
##      [,1] [,2] [,3]
## [1,] -1.5 -1.5 -1.5
## [2,] -0.5 -0.5 -0.5
## [3,]  0.5  0.5  0.5
## [4,]  1.5  1.5  1.5
# Return the result
dataPoints_Trans1
##      [,1] [,2] [,3]
## [1,] -1.5 -1.5 -1.5
## [2,] -0.5 -0.5 -0.5
## [3,]  0.5  0.5  0.5
## [4,]  1.5  1.5  1.5
# Normalize
dataPoints_Trans2 <- sweep(dataPoints_Trans1, 2, dataPoints_sdev, "/")

# Return the result
dataPoints_Trans2
##            [,1]       [,2]       [,3]
## [1,] -1.1618950 -1.1618950 -1.1618950
## [2,] -0.3872983 -0.3872983 -0.3872983
## [3,]  0.3872983  0.3872983  0.3872983
## [4,]  1.1618950  1.1618950  1.1618950

NB: для этого простой пример тот же результат, конечно, может быть достигнут легче
apply(dataPoints, 2, scale)