r - كيفية - منع التكرار في الاستعلام sql




وظائف التجميع(تبلي ، من قبل ، الكلي) و*تطبيق الأسرة (6)

عندما أريد أن أفعل شيئًا "خريطة" في R ، عادةً ما أحاول استخدام وظيفة في العائلة apply .

ومع ذلك ، فأنا لم أفهم أبدًا الاختلافات بينهم - كيف sapply { sapply ، lapply ، etc.} الوظيفة على الإدخال / lapply المدخلات ، أو ما سيبدو عليه الناتج ، أو حتى ما يمكن أن تكون عليه المدخلات أنا غالبا ما تذهب من خلال كل منهم حتى أحصل على ما أريد.

هل يمكن لأي شخص أن يفسر كيف يستخدم أي منها متى؟

فهمي الحالي (ربما غير صحيح / غير مكتمل) هو ...

  1. sapply(vec, f) : الإدخال هو متجه. الناتج هو متجه / مصفوفة ، حيث يكون العنصر i هو f(vec[i]) ، مما يتيح لك مصفوفة إذا كان f يحتوي على خرج متعدد العناصر

  2. lapply(vec, f) : نفس sapply ولكن الإخراج قائمة؟

  3. apply(matrix, 1/2, f) : الإدخال هو مصفوفة. الناتج هو متجه ، حيث يكون العنصر i هو f (صف / col i من المصفوفة)
  4. tapply(vector, grouping, f) : output عبارة عن مصفوفة / مصفوفة ، حيث يكون عنصر في المصفوفة / الصفيف هو قيمة f في مجموعة g من المتجه ، ويتم دفع g إلى أسماء الصف / col
  5. by(dataframe, grouping, f) : let g be a grouping. تطبيق على كل عمود من المجموعة / dataframe. جميلة طباعة التجميع وقيمة f في كل عمود.
  6. aggregate(matrix, grouping, f) : يشبه by ، ولكن بدلاً من طباعة الإخراج بشكل جميل ، يلتقط التجميع كل شيء في مخطط بيانات.

السؤال الجانبي: ما زلت لم أتعلم plyr أو إعادة تشكيل - هل plyr أو reshape استبدال جميع هذه بالكامل؟


ابدأ أولاً بالإجابة الممتازة لجوران - من المشكوك فيه أن أي شيء يمكن أن يكون أفضل.

ثم قد تساعد التعليمات الرمزية القصيرة التالية على تذكر التمييز بين كل. في حين أن بعضها واضح ، إلا أن البعض الآخر قد يكون أقل من ذلك - لذلك ستجد تبريرًا في مناقشات Joran.

فن الإستذكار

  • lapply هو تطبيق قائمة التي تعمل على قائمة أو ناقلات وإرجاع قائمة.
  • هو sapply بسيط (افتراضات الدالة إلى إرجاع متجه أو مصفوفة عندما يكون ذلك ممكناً)
  • vapply هو تطبيق تم التحقق منه (يسمح لكتابة نوع الكائن المطلوب أن يتم تحديده مسبقًا)
  • rapply هو متكرر ينطبق على القوائم المتداخلة ، أي القوائم ضمن القوائم
  • tapply هو تطبيق ذو علامات حيث تحدد العلامات المجموعات الفرعية
  • apply عام : يطبق دالة على صفوف أو أعمدة المصفوفة (أو بشكل أعم على أبعاد المصفوفة)

بناء الخلفية الصحيحة

إذا كان استخدام العائلة apply لا يزال يبدو غريبًا بعض الشيء بالنسبة إليك ، فقد يكون السبب أنك تفتقد إلى نقطة عرض رئيسية.

هذه المادتين يمكن أن تساعد. أنها توفر الخلفية اللازمة لتحفيز تقنيات البرمجة الوظيفية التي يتم توفيرها من قبل عائلة apply وظائف.

سوف يتعرف مستخدمو Lisp على النموذج على الفور. إذا لم تكن على دراية بـ Lisp ، فبمجرد الحصول على رأسك حول FP ، ستحصل على وجهة نظر قوية لاستخدامها في R - وسيكون apply أكثر منطقية.


ربما الجدير بالذكر 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 لإطارات البيانات بالكامل (كما هي مثل 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
## ...

في الملاحظة الجانبية ، إليك كيفية plyr وظائف 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() بسهولة إلى ldply() لإنتاج ناتج مفيد ، إلخ.

من الناحية النظرية ، تعلم plyr ليس أكثر صعوبة من فهم الوظائف الأساسية *apply .

plyr وظائف plyr و plyr تقريبا كل هذه الوظائف في plyr اليومي. ولكن أيضًا من مستند المقدمة إلى Plyr:

وظائف ذات الصلة tapply sweep ليس لها وظيفة المقابلة في plyr ، وتبقى مفيدة. merge مفيد للجمع بين الملخصات والبيانات الأصلية.


لقد اكتشفت مؤخرا وظيفة sweep مفيدة نوعا ما sweep هنا من أجل الاكتمال:

مسح

الفكرة الأساسية هي أن تجتاح صف الصف أو العمود الحكيم وترجع إلى صفيف معدل. مثال يوضح ذلك (المصدر: datacamp ):

لنفترض أن لديك مصفوفة وتريد standardize أساس العمود:

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

ملاحظة: لهذا المثال البسيط يمكن بالطبع تحقيق نفس النتيجة بسهولة أكبر
apply(dataPoints, 2, scale)


منذ أن أدركت أن الإجابات (الممتازة جدًا) لهذا الموضوع تفتقر إلى التفسيرات aggregate . هنا مساهمتي.

بواسطة

يمكن أن تكون الدالة 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 وخصائص class المختلفة ، على التوالي بالنسبة لـ 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     

يعمل بالفعل والنتيجة مدهشة للغاية. هو كائن من فئة ذلك على طول 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

الاختلافان tapply هما أن الوسيطة الثانية aggregate يجب أن تكون قائمة بينما يمكن أن يكون tapply (غير إلزامي) قائمة وأن إخراج aggregate هو إطار بيانات بينما يكون واحد من tapply هو array .

تكمن قوة aggregate أنه يمكنه التعامل مع مجموعات فرعية من البيانات بسهولة باستخدام وسيطة subset وأن لها طرقًا لكائنات ts formula كذلك.

هذه العناصر تجعل من السهل العمل بشكل عام مع تلك 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

هناك أوقات أخرى عندما لا نستخدمها أو 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 في مكالمة واحدة ولكن علينا حساب المتوسط ​​على طول كل عنصر ثم دمجها (لاحظ أيضًا أنه يجب علينا استدعاء 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)

أحيانًا تكون النتائج متشابهة والاختلافات في الفئة فقط (ثم كيفية إظهارها / طباعتها وليس فقط - مثال ، كيفية تجميعها) الكائن:

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

تحقق الشفرة السابقة نفس الهدف والنتائج ، وفي بعض النقاط ، ما هي الأداة المستخدمة هي مجرد مسألة ذوق واحتياجات شخصية ؛ الكائنين السابقين لهما احتياجات مختلفة للغاية من حيث الترحيل.


هناك الكثير من الإجابات الرائعة التي تناقش الاختلافات في حالات الاستخدام لكل وظيفة. لا أحد من الإجابة يناقش الاختلافات في الأداء. هذا سبب معقول ويتوقع وظائف مختلفة مختلف المدخلات وتنتج مخرجات مختلفة ، ولكن معظمها له هدف مشترك عام لتقييم من قبل مجموعات / مجموعات. إجابتي سوف تركز على الأداء. نظرًا إلى أعلاه ، يتم تضمين إنشاء المدخلات من المتجهات في التوقيت ، كما لا يتم قياس الوظيفة apply .

لقد اختبرت اثنين من وظائف مختلفة sum length في وقت واحد. حجم الاختبار هو 50M على المدخلات و 50 K على الانتاج. لقد قمت أيضًا بتضمين data.table dplyr حاليًا لم تكن مستخدمة على نطاق واسع في الوقت الذي طُرح فيه سؤال ، data.table و dplyr . كلاهما يستحق بالتأكيد أن ننظر إذا كنت تهدف إلى الأداء الجيد.

library(dplyr)
library(data.table)
set.seed(123)
n = 5e7
k = 5e5
x = runif(n)
grp = sample(k, n, TRUE)

timing = list()

# sapply
timing[["sapply"]] = system.time({
    lt = split(x, grp)
    r.sapply = sapply(lt, function(x) list(sum(x), length(x)), simplify = FALSE)
})

# lapply
timing[["lapply"]] = system.time({
    lt = split(x, grp)
    r.lapply = lapply(lt, function(x) list(sum(x), length(x)))
})

# tapply
timing[["tapply"]] = system.time(
    r.tapply <- tapply(x, list(grp), function(x) list(sum(x), length(x)))
)

# by
timing[["by"]] = system.time(
    r.by <- by(x, list(grp), function(x) list(sum(x), length(x)), simplify = FALSE)
)

# aggregate
timing[["aggregate"]] = system.time(
    r.aggregate <- aggregate(x, list(grp), function(x) list(sum(x), length(x)), simplify = FALSE)
)

# dplyr
timing[["dplyr"]] = system.time({
    df = data_frame(x, grp)
    r.dplyr = summarise(group_by(df, grp), sum(x), n())
})

# data.table
timing[["data.table"]] = system.time({
    dt = setnames(setDT(list(x, grp)), c("x","grp"))
    r.data.table = dt[, .(sum(x), .N), grp]
})

# all output size match to group count
sapply(list(sapply=r.sapply, lapply=r.lapply, tapply=r.tapply, by=r.by, aggregate=r.aggregate, dplyr=r.dplyr, data.table=r.data.table), 
       function(x) (if(is.data.frame(x)) nrow else length)(x)==k)
#    sapply     lapply     tapply         by  aggregate      dplyr data.table 
#      TRUE       TRUE       TRUE       TRUE       TRUE       TRUE       TRUE 

# print timings
as.data.table(sapply(timing, `[[`, "elapsed"), keep.rownames = TRUE
              )[,.(fun = V1, elapsed = V2)
                ][order(-elapsed)]
#          fun elapsed
#1:  aggregate 109.139
#2:         by  25.738
#3:      dplyr  18.978
#4:     tapply  17.006
#5:     lapply  11.524
#6:     sapply  11.326
#7: data.table   2.686




r-faq