r - كيفية - منع التكرار في الاستعلام sql
وظائف التجميع(تبلي ، من قبل ، الكلي) و*تطبيق الأسرة (6)
عندما أريد أن أفعل شيئًا "خريطة" في R ، عادةً ما أحاول استخدام وظيفة في العائلة apply
.
ومع ذلك ، فأنا لم أفهم أبدًا الاختلافات بينهم - كيف sapply
{ sapply
، lapply
، etc.} الوظيفة على الإدخال / lapply
المدخلات ، أو ما سيبدو عليه الناتج ، أو حتى ما يمكن أن تكون عليه المدخلات أنا غالبا ما تذهب من خلال كل منهم حتى أحصل على ما أريد.
هل يمكن لأي شخص أن يفسر كيف يستخدم أي منها متى؟
فهمي الحالي (ربما غير صحيح / غير مكتمل) هو ...
sapply(vec, f)
: الإدخال هو متجه. الناتج هو متجه / مصفوفة ، حيث يكون العنصرi
هوf(vec[i])
، مما يتيح لك مصفوفة إذا كانf
يحتوي على خرج متعدد العناصرlapply(vec, f)
: نفسsapply
ولكن الإخراج قائمة؟-
apply(matrix, 1/2, f)
: الإدخال هو مصفوفة. الناتج هو متجه ، حيث يكون العنصرi
هو f (صف / col i من المصفوفة) -
tapply(vector, grouping, f)
: output عبارة عن مصفوفة / مصفوفة ، حيث يكون عنصر في المصفوفة / الصفيف هو قيمةf
في مجموعةg
من المتجه ، ويتم دفعg
إلى أسماء الصف / col -
by(dataframe, grouping, f)
: letg
be a grouping. تطبيق على كل عمود من المجموعة / dataframe. جميلة طباعة التجميع وقيمةf
في كل عمود. -
aggregate(matrix, grouping, f)
: يشبهby
، ولكن بدلاً من طباعة الإخراج بشكل جميل ، يلتقط التجميع كل شيء في مخطط بيانات.
السؤال الجانبي: ما زلت لم أتعلم plyr أو إعادة تشكيل - هل plyr
أو reshape
استبدال جميع هذه بالكامل؟
ابدأ أولاً بالإجابة الممتازة لجوران - من المشكوك فيه أن أي شيء يمكن أن يكون أفضل.
ثم قد تساعد التعليمات الرمزية القصيرة التالية على تذكر التمييز بين كل. في حين أن بعضها واضح ، إلا أن البعض الآخر قد يكون أقل من ذلك - لذلك ستجد تبريرًا في مناقشات Joran.
فن الإستذكار
-
lapply
هو تطبيق قائمة التي تعمل على قائمة أو ناقلات وإرجاع قائمة. - هو
sapply
بسيط (افتراضات الدالة إلى إرجاع متجه أو مصفوفة عندما يكون ذلك ممكناً) -
vapply
هو تطبيق تم التحقق منه (يسمح لكتابة نوع الكائن المطلوب أن يتم تحديده مسبقًا) -
rapply
هو متكرر ينطبق على القوائم المتداخلة ، أي القوائم ضمن القوائم -
tapply
هو تطبيق ذو علامات حيث تحدد العلامات المجموعات الفرعية -
apply
عام : يطبق دالة على صفوف أو أعمدة المصفوفة (أو بشكل أعم على أبعاد المصفوفة)
بناء الخلفية الصحيحة
إذا كان استخدام العائلة apply
لا يزال يبدو غريبًا بعض الشيء بالنسبة إليك ، فقد يكون السبب أنك تفتقد إلى نقطة عرض رئيسية.
هذه المادتين يمكن أن تساعد. أنها توفر الخلفية اللازمة لتحفيز تقنيات البرمجة الوظيفية التي يتم توفيرها من قبل عائلة apply
وظائف.
سوف يتعرف مستخدمو Lisp على النموذج على الفور. إذا لم تكن على دراية بـ Lisp ، فبمجرد الحصول على رأسك حول FP ، ستحصل على وجهة نظر قوية لاستخدامها في R - وسيكون apply
أكثر منطقية.
- Advanced R: البرمجة الوظيفية ، بقلم هادلي ويكهام
- برمجة وظيفية بسيطة في R ، من قبل مايكل بارتون
ربما الجدير بالذكر 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