توابع Apply در زبان برنامه نویسی R — راهنمای کاربردی

۳۰۱ بازدید
آخرین به‌روزرسانی: ۶ خرداد ۱۴۰۲
زمان مطالعه: ۱۴ دقیقه
R appy family functions

اغلب برای انجام کارها یا محاسبات یکسان بر روی ستون‌های یک مجموعه داده، از حلقه‌های تکرار یا loop استفاده می‌شود. به این منظور در زبان برنامه‌نویسی R خانواده توابع apply مانند $$apply(), lapply(), sapply(), mapply()$$ و $$tapply()$$ وجود دارند که این عملیات تکراری را به واسطه محاسبات ماتریسی انجام می‌دهند، در نتیجه سرعت بالا و انعطاف زیادی دارند.

در این آموزش به کمک محیط Rstudio و زبان برنامه‌نویسی R، با مجموعه یا خانواده توابع apply آشنا شده، کاربردهای هر یک را به کمک مثال‌هایی مرورر می‌کنیم. البته دستورات و کدها را می‌توانید به تنهایی در محیط زبان برنامه‌نویسی R نیز اجرا کنید.

خانواده توابع Apply

در زبان برنامه‌نویسی R، یکی از معروف‌ترین گروه توابع، اعضای خانواده توابع apply‌ هستند که بخصوص در انجام محاسبات روی ستون‌ها یا سطرهای ماتریس به خوبی عمل می‌کنند. به همین علت در این مطلب قصد داریم با ارائه مثال‌های کاربردی، از این توابع استفاده کنیم.

اعضای خانواده توابع apply قادر به انجام محاسبات براساس توابع دیگر روی مجموعه‌ای از «چارچوب داده» (Dataframe)، «لیست‌ها» (List)، «بردار» (Vector) یا« ماتریس» (Matrix) و ... هستند. به این ترتیب با استفاده از این توابع قادر هستیم محاسبات مربوط به یک تابع دیگر را بر روی داده‌ها «اعمال» (apply) کنیم. بر همین اساس مشخص است که باید یکی از پارامترهای این گروه توابع، نام تابع دیگری باشد که قرار است به صورت تکراری روی اجزای «ساختارهای داده‌ها» (Data Structure)، اعمال شود.

تابع $$apply()$$

برای محاسبه یک تابع روی ماتریس‌ یا آرایه می‌توان از تابع $$apply()$$‌ استفاده کرد. این تابع دارای 3 پارامتر اصلی و یک یا چند پارامتر اختیاری است که در ادامه با آن‌ها آشنا می‌شویم.

apply(X, MARGIN, FUN)
Here:
-x: an array or matrix
-MARGIN:  take a value or range between 1 and 2 to define where to apply the function:
-MARGIN=1`: the manipulation is performed on rows
-MARGIN=2`: the manipulation is performed on columns
-MARGIN=c(1,2)` the manipulation is performed on rows and columns
-FUN: tells which function to apply. Built functions like mean, median, sum, min, max and even user-defined functions can be applied>
...

به منظور اطلاع از نحوه عملکرد هر یک از این پارامترها بهتر است به جدول زیر توجه کنید.

پارامتر عملکرد
x معرفی یک ماتریس یا آرایه
MARGIN تعیین بُعدی که باید محاسبات در آن اجرا شود. اجرای محاسبات روی سطرهای ماتریس، MARGIN=1، اجرای محاسبات روی ستون‌های ماتریس، MARGIN=2، اجرای محاسبات روی تقاطع سطرها و ستون‌ها، MARGIN=$$c(1,2)$$، که در حقیقت این شکل پارامتر، سلول‌ها را نشانه خواهد گرفت.
FUN نام تابع که باید برای سطرها یا ستون‌ها اعمال شود. مانند mean, median, max, ... و حتی توابعی که توسط کاربر معرفی شده‌اند.
... اگر تابع FUN‌ احتیاج به پارامترهایی داشته باشد، می‌توان آن را به عنوان پارامترهای دیگر تابع $$apply()$$ معرفی کرد.

مثال

به عنوان یک مثال ساده، ماتریسی را در نظر بگیرید که دارای ۵ سطر و ۶ ستون است که مولفه یا درایه‌های آن از ۱ تا ۱۰ چیده شده‌اند. هدف از اجرای تابع $$apply()$$ محاسبه جمع هر ستون است. این ماتریس در متغیر m1‌ ذخیره شده است و مجموع ستون‌ها نیز در متغیر a_m1 محاسبه می‌شود. کدهای زیر به این منظور نوشته شده‌اند.

m1 <- matrix(C<-(1:10),nrow=5, ncol=6)
m1
a_m1 <- apply(m1, 2, sum)
a_m1

با اجرای این کد، محاسبات صورت گرفته و خروجی به صورت زیر در خواهد آمد.

apply

نکته: از آنجایی که ماتریس دارای ۳۰ درایه است و فقط مقدارهای ۱ تا ۱۰ باید در آن قرار گیرند، نرم‌افزار با تکرار اعداد ۱ تا ۱۰ ماتریس را به ترتیب ستون‌ها، کامل کرده است.

نتیجه اجرای تابع $$apply()$$ می‌تواند یک بردار، یک ماتریس یا حتی یک عدد باشد. برای مثال فرض کنید که کد بالا را به صورتی در آوریم که هر درایه از ماتریس را به توان ۲ رسانده و یک ماتریس جدید بسازد. به این منظور تابعی به نام sq ایجاده کرده‌ایم تا هر مقداری را به توان ۲ برساند. در انتهای کد نیز مشخص است که با استفاده تابع $$class()$$ ماهیت a_m1 درخواست شده است.

m1 <- matrix(c(1:10),nrow=5, ncol=6)
m1
sq=function(x) x^2
a_m1 <- apply(m1, c(1,2),sq)
a_m1
class(a_m1)

در این حالت خروجی به صورت زیر ظاهر خواهد شد. در سطر آخر کلاس متغیر a_m1 به صورت matrix معرفی شده است.

> m1 <- matrix(c(1:10),nrow=5, ncol=6)
> m1
     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]    1    6    1    6    1    6
[2,]    2    7    2    7    2    7
[3,]    3    8    3    8    3    8
[4,]    4    9    4    9    4    9
[5,]    5   10    5   10    5   10
> sq=function(x) x^2
> a_m1 <- apply(m1, c(1,2),sq)
> a_m1
     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]    1   36    1   36    1   36
[2,]    4   49    4   49    4   49
[3,]    9   64    9   64    9   64
[4,]   16   81   16   81   16   81
[5,]   25  100   25  100   25  100
> class(a_m1)
[1] "matrix"

مثال

با استفاده از ۳۰ عدد تصادفی، یک ماتریس ۵ سطری و ۶ ستونی ایجاد کرده‌ایم. هدف محاسبه میانگین برای سطرهای این ماتریس است. البته این کار را با استفاده از تابع $$mean()$$ انجام داده‌ایم. همچنین برای حذف نقطه‌های دورافتاده یا پرت از میانگین پیراسته (Trimmed mean) در محاسبات بهره برده‌ایم.

همانطور که در کد زیر دیده می‌شود، پارامتر trim=0.2 باعث می‌شود که ۲۰ درصد از بزرگترین و کوچکترین داده‌ها در محاسبه میانگین اصلاح شده نقشی نداشته باشند. این پارامتر مربوط به تابع $$mean()$$ است که به عنوان پارامتر اختیاری تابع $$apply()$$ در انتها ظاهر شده است.

m1 <- matrix(rnorm(30),nrow=5, ncol=6)
m1
a_m1 <- apply(m1, 1,mean)
a_m2 <- apply(m1, 1,mean,trim=0.2)
a_m1
a_m2

با توجه به خروجی ارائه شده، تفاوت در نتایج حاصل شده در متغیرهای a_m1 و a_m2 دیده می‌شود. مشخص است که a_m1 بدون میانگین و a_m2 میانگین پیراسته را محاسبه کرده است.

> m1 <- matrix(rnorm(30),nrow=5, ncol=6)

> m1
            [,1]       [,2]        [,3]       [,4]        [,5]       [,6]
[1,] -2.05257158 -0.8285476  0.30718680  1.3715670 -0.74737752  1.1675458
[2,]  1.87029090  1.1464947  1.74230249  0.3695404 -0.15568080  1.4076545
[3,] -0.66610135 -0.9260289 -1.32928373 -0.1576227  1.40117008 -0.5165439
[4,] -0.01877978 -0.7377102 -0.09684306 -0.1688754  2.90072436 -0.5722849
[5,]  0.40026689  0.5550915  1.99883076  0.8466858 -0.01250701 -0.5998639

> a_m1 <- apply(m1, 1,mean)

> a_m2 <- apply(m1, 1,mean,trim=0.2)

> a_m1
[1] -0.1303662  1.0634337 -0.3657351  0.2177052  0.5314173

> a_m2
[1] -0.02529815  1.16649803 -0.56657421 -0.21419579  0.44738430
>

تصویر زیر شکل محاسبه جمع، برای ستون‌های چنین ماتریسی را نشان می‌دهد. مشخص است که حاصل یک بردار خواهد بود. البته توجه داشته باشید که از آنجایی که اعداد به صورت تصادفی تولید شده‌اند ممکن است نتایج حاصل از محاسبات انجام شده در این تصویر یا مثال قبلی با نتایج اجرای کد توسط شما یکسان نباشد.

apply function

تابع $$lapply()$$

یکی دیگر از توابع خانواده apply، تابع $$lapply()$$ است که قالب خروجی آن به صورت یک لیست است. به همین علت حرف انگلیسی «l» به عنوان پیشوند این تابع به کار رفته است.

برای انجام محاسبات روی لیست‌ها و حتی چارچوب داده (Dataframe) از این تابع استفاده می‌شود. تابعی که در پارامتر FUN معرفی می‌کنید برای همه درایه‌ها یا مولفه‌های ساختار داده محاسبه خواهد شد و حاصل محاسبات به صورت «ساختار داده لیست» (List Structure) ثبت و ارائه خواهد شد.

lapply(X, FUN)
Arguments:
-X: A vector or an object
-FUN: Function applied to each element of x	
...: optional arguments to FUN.

همانطور که دیده می‌شود، در این تابع پارامتر MARGIN احتیاجی نیست و تابع مورد نظر روی همه درایه‌ها اعمال خواهد شد. با توجه به مثال قبل فرض کنید تابع sq را می‌خواهیم برای همه عناصر ماتریس به کار ببریم. کافی است با تابع $$lapply()$$ مطابق با کد زیر محاسبات را انجام دهیم. نتیجه درست به مانند حالت قبل خواهد شد با این تفاوت که ماهیت خروجی در اینجا یک لیست است.

la_m1=lapply(m1,sq)
la_m1
unlistla_m1=unlist(la_m1)
unlistla_m1

البته در انتهای کد، متغیر حاصل از اجرای تابع $$lapply()$$ را به کمک تابع $$unlist()$$ از حالت لیست خارج کرده‌ایم. خروجی به صورت زیر است:

> la_m1
[[1]]
[1] 1

[[2]]
[1] 4

[[3]]
[1] 9

[[4]]
[1] 16

[[5]]
[1] 25

[[6]]
[1] 36

[[7]]
[1] 49

[[8]]
[1] 64

[[9]]
[1] 81

[[10]]
[1] 100

[[11]]
[1] 1

[[12]]
[1] 4

[[13]]
[1] 9

[[14]]
[1] 16

[[15]]
[1] 25

[[16]]
[1] 36

[[17]]
[1] 49

[[18]]
[1] 64

[[19]]
[1] 81

[[20]]
[1] 100

[[21]]
[1] 1

[[22]]
[1] 4

[[23]]
[1] 9

[[24]]
[1] 16

[[25]]
[1] 25

[[26]]
[1] 36

[[27]]
[1] 49

[[28]]
[1] 64

[[29]]
[1] 81

[[30]]
[1] 100

> unlistla_m1=unlist(la_m1)
> unlistla_m1
 [1]   1   4   9  16  25  36  49  64  81 100   1   4   9  16  25  36  49  64  81 100   1   4   9  16  25  36  49  64  81 100
>

مثال

در اینجا به بررسی یک مثال برای داده‌های متنی می‌پردازیم. فرض کنید در متغیر pnames اسامی چهار نفر با حروف بزرگ نوشته شده است. می‌خواهیم همه اسامی را به حروف کوچک تبدیل کنیم. با استفاده از تابع $$lapply()$$ این کار به راحتی امکان‌پذیر است.

pnames <- c("GOERGE","DAVID","CHARLS","FIGO")
pnames_lower <-lapply(pnames, tolower)
pnames_lower
str(pnames_lower)

مشخص است که با استفاده از تابع $$tolower()$$ حروف بزرگ به کوچک تبدیل شده‌اند و این کار برای همه عناصر لیست صورت گرفته. به این ترتیب خروجی به صورت زیر خواهد بود.

> pnames <- c("GOERGE","DAVID","CHARLS","FIGO")
> pnames_lower <-lapply(pnames, tolower)
> pnames_lower
[[1]]
[1] "goerge"

[[2]]
[1] "david"

[[3]]
[1] "charls"

[[4]]
[1] "figo"

> str(pnames_lower)
List of 4
 $ : chr "goerge"
 $ : chr "david"
 $ : chr "charls"
 $ : chr "figo"

به منظور تبدیل این لیست به یک بردار متنی، از تابع $$unlist()$$ استفاده خواهیم کرد. به این ترتیب خواهیم داشت:

pnames_lower <-unlist(lapply(pnames,tolower))
str(pnames_lower)

و خروجی به شکل زیر در خواهد آمد.

> pnames_lower <-unlist(lapply(pnames,tolower))
> str(pnames_lower)
 chr [1:4] "goerge" "david" "charls" "figo"
>

تابع $$sapply()$$

اگر می‌خواهید خروجی محاسبات به صورت یک بردار درآید از تابع $$sapply()$$ به جای $$lapply()$$ استفاده کنید.

پارامترهای این تابع به صورت زیر است.

sapply(X, FUN)
Arguments:
-X: A vector or an object
-FUN: Function applied to each element of x
...: optional arguments to FUN.

برای مثال فرض کنید که می‌خواهیم برای مجموعه داده‌های cars که به صورت پیش‌فرض در R قرار دارد، حداقل «سرعت» (Speed) و «مسافت توقف» (dist) را محاسبه کنید. دستورات زیر براساس توابع $$lapply()$$ و $$sapply()$$ این محاسبات را انجام می‌دهند.

head(cars)
dt <- cars
lmn_cars <- lapply(dt, min)
smn_cars <- sapply(dt, min)
lmn_cars
smn_cars

به شکل خروجی هر دو تابع توجه کنید. اولین خروجی به صورت لیست توسط تابع $$lapply()$$ و دومین خروجی به صورت یک بردار و توسط تابع $$sapply()$$‌ ایجاد شده است.

> head(cars)
  speed dist
1     4    2
2     4   10
3     7    4
4     7   22
5     8   16
6     9   10
> dt <- cars
> lmn_cars <- lapply(dt, min)
> smn_cars <- sapply(dt, min)
> lmn_cars
$`speed`
[1] 4

$dist
[1] 2

> smn_cars
speed  dist 
    4     2 
>

حال فرض کنید که بخواهیم به کمک یک تابع، براساس حداکثر و حداقل مقدارهای محاسبه شده، میانگین را محاسبه کنیم. به این ترتیب باید با تعریف یک تابع جدید و استفاده از این دو مقدار، محاسبات را پیش ببریم.

پس از محاسبه حداقل و حداکثر مقدارها با تابع $$sapply()$$، به تعریف تابع $$avg()$$ می‌پردازیم، سپس از تابع $$sapply()$$ برای محاسبه میانگین هر دو متغیر سرعت و مسافت به کمک تابع $$avg()$$ می‌پردازیم.

head(cars)
dt <- cars
lmn_cars <- lapply(dt, min)
smn_cars <- sapply(dt, min)
lmn_cars
smn_cars

lmxcars <- lapply(dt, max)
smxcars <- sapply(dt, max)
lmxcars

avg <- function(x) {  
  ( min(x) + max(x) ) / 2}
fcars <- sapply(dt, avg)
fcars

از آنجایی که از تابع $$sapply()$$‌ استفاده کرده‌ایم، خروجی براساس این کد یک آرایه یا بردار با مقدارهای 14.5 و 61.0 خواهد بود که به ترتیب میانگین سرعت و مسافت را نشان می‌دهند.

> dt <- cars
> lmn_cars <- lapply(dt, min)
> smn_cars <- sapply(dt, min)
> lmn_cars
$`speed`
[1] 4

$dist
[1] 2

> smn_cars
speed  dist 
    4     2 
> 
> lmxcars <- lapply(dt, max)
> smxcars <- sapply(dt, max)
> lmxcars
$`speed`
[1] 25

$dist
[1] 120

> 
> avg <- function(x) {  
+   ( min(x) + max(x) ) / 2}
> fcars <- sapply(dt, avg)
> fcars
speed  dist 
 14.5  61.0 
>

مشخص است که متغیرهای lmn و smn برای محاسبه حداقل و متغیرهای lmx و smx برای حداکثر در نظر گرفته شده‌اند.

طبقه‌بندی داده‌ها

یکی دیگر از کاربردهای تابع $$lappy()$$ و $$sapply()$$ تفکیک یک چارچوب داده است. در ادامه خواهید دید که به کمک تابع $$below\_avg()$$ داده‌های مربوط به سرعت و فاصله را به دو دسته «بیشتر از میانگین» و «کمتر از میانگین» تقسیم کرده‌ایم. ابتدا به تعریف تابع اصلی که براساس آن تفکیک صورت می‌گیرد، می‌پردازیم.

below_ave <- function(x) {  
    ave <- mean(x) 
    return(x[x < ave])
}

همانطور که مشخص است در تابع $$below\_avg()$$، پس از محاسبه میانگین برای هر یک از بردارها، مقدارهایی که کمتر از میانگین هستند، جدا شده‌اند. به این ترتیب با استفاده از تابع $$sapply()$$ یا $$lapply()$$ عمل تفکیک صورت می‌گیرد.

dt_s<- sapply(dt, below_ave)
dt_l<- lapply(dt, below_ave)
dt_l
dt_s
identical(dt_s, dt_l)

همانطور که در انتهای کد می‌بینید، یکسان بودن نتایج دو تابع $$sapply()$$ و $$lapply()$$ با تابع $$identical()$$ بررسی شده است. خروجی به صورت زیر خواهد بود.

> dt_s<- sapply(dt, below_ave)
> dt_l<- lapply(dt, below_ave)
> dt_l
$`speed`
 [1] 16 16 17 17 17 18 18 18 18 19 19 19 20 20 20 20 20 22 23 24 24 24 24 25

$dist
 [1]  46  60  80  54  50  56  76  84  46  68  48  52  56  64  66  54  70  92  93 120  85

> dt_s
$`speed`
 [1] 16 16 17 17 17 18 18 18 18 19 19 19 20 20 20 20 20 22 23 24 24 24 24 25

$dist
 [1]  46  60  80  54  50  56  76  84  46  68  48  52  56  64  66  54  70  92  93 120  85

> identical(dt_s, dt_l)
[1] TRUE

تابع $$tapply()$$

تا اینجا مشخص شد که محاسبات روی سطر، ستون و یا درایه‌های یک ماتریس یا چارچوب داده، توسط توابع $$apply()$$، $$lapply()$$ و $$sapply()$$ صورت می‌گیرد. ولی تابع $$tapply()$$ یک ویژگی مهم نسبت به دیگر خانواده توابع $$apply()$$ دارد.

با استفاده از تابع $$tapply()$$ می‌توان محاسبه را براساس یک متغیر «عامل» (Factor) جداگانه انجام داد. به این ترتیب داده‌ها براساس سطوح مختلف یک متغیر عامل طبقه‌بندی شده و محاسبه تابع (مثلا میانگین) برای هر طبقه جداگانه صورت می‌گیرد.

پارامترهای این تابع به صورت زیر هستند.

tapply(X, INDEX, FUN = NULL)
Arguments:
-X: An object, usually a vector
-INDEX: A list containing factor
-FUN: Function applied to each element of x
...: optional arguments to FUN.

مشخص است که بیشتر پارامترهای این تابع به جز INDEX مانند توابع دیگر خانواده apply هستند. در اینجا INDEX بیانگر لیستی است که شامل متغیر عامل است. از آنجایی که این پارامتر نقش مهم در محاسبه تابع $$tapply()$$ ایفا می‌کند، از یک مثال به منظور روشن شدن نقش این پارامتر، کمک می‌گیریم.

فرض کنید با مجموعه داده‌های iris که ویژگی‌های کمی یک نمونه ۱۵۰ تایی از سه نوع گل زنبق مختلف را ثبت کرده است سروکار داریم. این مجموعه داده تقریبا در همه مثال‌های «یادگیری ماشین» (Machine Learning) استفاده می‌شود. این ویژگی‌ها، شامل طول و عرض کاسبرگ و گلبرگ سه نوع زنبق (setosa, versicolor, virginica) است که توسط دانشمند آمار «رونالد فیشر» (Roanld Fisher) جمع‌آوری شده. در اینجا هدف محاسبه میانگین برای عرض کاسبرگ (Sepal.Width) است و  متغیر عامل هم نوع گل زنبق یعنی Species است. در کد زیر ابتدا چند مشاهده مختلف از این مجموعه داده نمایش داده شده، سپس به کمک تابع $$tapply()$$ محاسبه میانگین برای عرض کاسبرگ به تفکیک نوع گل صورت می‌پذیرد.

smp=sample(1:150,10)
iris[smp,]
ts=tapply(iris$Sepal.Width, iris$Species, mean)
ts

خروجی برای کد بالا به صورت زیر خواهد بود.

> smp=sample(1:150,10)
> iris[smp,]
    Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
3            4.7         3.2          1.3         0.2     setosa
40           5.1         3.4          1.5         0.2     setosa
79           6.0         2.9          4.5         1.5 versicolor
102          5.8         2.7          5.1         1.9  virginica
39           4.4         3.0          1.3         0.2     setosa
14           4.3         3.0          1.1         0.1     setosa
74           6.1         2.8          4.7         1.2 versicolor
129          6.4         2.8          5.6         2.1  virginica
134          6.3         2.8          5.1         1.5  virginica
69           6.2         2.2          4.5         1.5 versicolor
> ts=tapply(iris$Sepal.Width, iris$Species, mean)
> ts
    setosa versicolor  virginica 
     3.428      2.770      2.974 
>

همانطور که دیده می‌شود، دستور sample یک نمونه از اعداد ۱ تا ۱۵۰ تولید می‌کند. سپس اعضای مجموعه داده iris با شماره این سطرها نمایش داده شده است. در انتها نیز نتایج حاصل از تابع $$tapply()$$ ظاهر شده که میانگین عرض کاسبرگ را برای هر سه نوع گل‌ زنبق نشان می‌دهد. اگر لازم باشد که میانگین برای همه چهار ویژگی‌ گل‌ها، محاسبه و به تفکیک نوع گل ظاهر شود، بهتر است از کد زیر استفاده کنید.

# tsapply and compute mean of each column
tsepw=tapply(iris$Sepal.Width, iris$Species, mean)
tsepl=tapply(iris$Sepal.Length, iris$Species, mean)
tpepw=tapply(iris$Petal.Width, iris$Species, mean)
tpepl=tapply(iris$Petal.Length, iris$Species, mean)
# Combined Output
tslist=cbind(tsepw,tsepl,tpepw,tpepl)
colnames(tslist) =c("Mean S.Width","Mean S.Length","Mean P.Width","Mean P.Length")
# Showing Output
tslist

مشخص است که برای محاسبه میانگین برای همه ویژگی‌ها، مجبور شده‌ایم که به ازای هر ویژگی، یکبار از تابع $$tapply()$$ استفاده کنیم. در انتها نیز با ترکیب و نام‌گذاری آن‌ها خروجی را به صورت زیر تشکیل داده‌ایم.

> tslist
           Mean S.Width Mean S.Length Mean P.Width Mean P.Length
setosa            3.428         5.006        0.246         1.462
versicolor        2.770         5.936        1.326         4.260
virginica         2.974         6.588        2.026         5.552
>

نکته: تابع $$tapply()$$ نمی‌تواند همزمان بر روی چند متغیر، تابع مورد نظر را محاسبه کند، به همین علت برای هر ستون از ویژگی‌ها یکبار از تابع $$tapply()$$ استفاده کرده‌ایم.

ترکیب توابع در $$tapply()$$

فرض کنید که همزمان به چند شاخص آماری برای یک متغیر در مجموعه داده احتیاج دارید و می‌خواهید براساس یک متغیر عامل، آن ها را محاسبه کنید. همانطور که به یاد دارید، خانواده توابع apply فقط می‌توانند یک تابع را برای انجام محاسبات بپذیرند. به این ترتیب اگر در مثال قبل احتیاج به چند شاخص آماری نظیر میانگین، میانه و مجموع متغیر عرض کاسبرگ (Sepal.width) داشته باشید بهتر است به صورت زیر عمل کنید. ابتدا یک تابع تعریف می‌کنیم و شاخص‌های مورد نیاز را در آن محاسبه می‌کنیم. سپس از این تابع به عنوان پارامتر خانواده توابع apply کمک می‌گیریم. به مثال زیر دقت کنید.

مثال

میانگین، میانه و مجموع عرض کاسبرگ‌های گل‌های زنبق به تفکیک نوع گل براساس کدی که در زیر نوشته شده است، امکان‌پذیر است.

iris
s=function(x) x=list(mean(x),median(x),sum(x))
ts=tapply(iris$Sepal.Width, iris$Species, s)
names(ts)=c("Mean ","Median ", "Sum" )
tp=unlist(ts)
ts
names(tp)=c("Mean Setosa","Median Setosa", 
"Sum Setosa","Mean versicolor","Median versicolor",
 "Sum versicolor" ,"Mean virginica","Median virginica", "Sum virginica")
tp

همانطور که می‌بینید، ابتدا تابع s همه محاسبات را انجام داده و در قالب یک لیست قرار می‌دهد. سپس با اجرای تابع $$tapply()$$ به همراه پارامتر تابع s محاسبات مورد نظر انجام گرفته و در انتها نیز لیست ارائه شده، نام‌گذاری و نمایش داده شده است. خروجی این برنامه به صورت زیر خواهد بود.

> ts
$`Mean `
$`Mean `[[1]]
[1] 3.428

$`Mean `[[2]]
[1] 3.4

$`Mean `[[3]]
[1] 171.4


$`Median `
$`Median `[[1]]
[1] 2.77

$`Median `[[2]]
[1] 2.8

$`Median `[[3]]
[1] 138.5


$Sum
$Sum[[1]]
[1] 2.974

$Sum[[2]]
[1] 3

$Sum[[3]]
[1] 148.7


> names(tp)=c("Mean Setosa","Median Setosa", 
+             "Sum Setosa","Mean versicolor","Median versicolor",
+             "Sum versicolor" ,"Mean virginica","Median virginica", "Sum virginica")
> tp
      Mean Setosa     Median Setosa        Sum Setosa   Mean versicolor Median versicolor    Sum versicolor 
            3.428             3.400           171.400             2.770             2.800           138.500 
   Mean virginica  Median virginica     Sum virginica 
            2.974             3.000           148.700 
>

تابع $$mapply()$$

نسخه چند متغیره تابع $$lapply()$$ را می‌توان تابع $$mapply()$$‌ در نظر گرفت. به این ترتیب می‌توان پارامترها را به صورت برداری، معرفی کرد.

فرض کنید براساس مثال قبل لازم است که میانگین، میانه و جمع هر یک از متغیرهای مربوط به داده‌های iris (بدون در نظر گرفتن ستون آخر که نوع گل را نشان می‌دهد) را محاسبه کنیم. برای این کار از کد زیر کمک می‌گیریم و برنامه را با کمترین میزان دستورات اجرا می‌کنیم. کدها را با مثال قبل مقایسه کنید.

mp=mapply(s,iris[,1:4])
mp

مشخص است که برای تعیین نوع محاسبه، باز هم از تابع s کمک گرفته‌ایم. با اجرای این کد خروجی برنامه به صورت زیر در خواهد آمد.

> mp
     Sepal.Length Sepal.Width Petal.Length Petal.Width
[1,] 5.843333     3.057333    3.758        1.199333   
[2,] 5.8          3           4.35         1.3        
[3,] 876.5        458.6       563.7        179.9      
>

در اینجا، سطر اول میانگین، سطر دوم میانه و سطر آخر نیز مجموع مقدارهای هر یک از متغیرها را نشان می‌دهد. در این حالت دیگر مجبور نبودیم برای هر یک متغیرها، محاسبات را جداگانه انجام دهیم.

مثال

فرض کنید می‌خواهیم یک ماتریس ۴ در ۴ بسازیم که دارای مقادیر از ۱ تا ۴ باشد. البته می‌دانید که این کار به واسطه دستور matrix‌ نیز امکان پذیر است. به کد زیر توجه کنید. مشخص است که پارامتر byrow=TRUE تعیین می‌کند که مقدارها باید سطر به سطر در ماتریس چیده شوند.

> matrix(rep(1:4,4),4,byrow=TRUE)
     [,1] [,2] [,3] [,4]
[1,]    1    2    3    4
[2,]    1    2    3    4
[3,]    1    2    3    4
[4,]    1    2    3    4
>

حلا سعی می‌کنیم به کمک تابع $$mapply()$$ و تعیین پارامترهای آن طوری عمل کنیم عین همین ماتریس ایجاد شود.

> mp=mapply(rep,1:4,4)
> mp
     [,1] [,2] [,3] [,4]
[1,]    1    2    3    4
[2,]    1    2    3    4
[3,]    1    2    3    4
[4,]    1    2    3    4
>

به این ترتیب تابع rep که عمل تکرار را انجام می‌دهد با پارامترهای 1:4 و 4 اجرا شده و در نتیجه ماتریس ایجاد می‌شود. مشخص است که اینجا، پارامتر اول که با 1:4 مشخص شده است ارقامی است که باید ماتریس را بسازند و پارامتر دوم یعنی ۴ تیز تعداد تکرار را تعیین می‌کند. بنابراین یک ماتریس ۴ در ۴ با درایه‌های تکراری ۱، ۲، ۳ و ۴ ساخته می‌شود.

خلاصه

براساس ویژگی‌ها و شیوه محاسبه هر یک از اعضای خانواده توابع apply جدول زیر تهیه شده است تا بهتر عملکرد هر یک از توابع مشخص و با دیگر توابع این گروه مقایسه شود.

تابع پارامترها شیوه محاسبه ورودی خروجی
$$apply()$$ apply(x, MARGIN, FUN) محاسبه روی سطر یا ستون ماتریس یا آرایه بردار، لیست یا آرایه
$$lapply()$$ lapply(x, FUN) محاسبه روی مولفه‌ها لیست، بردار یا آرایه لیست
$$sapply()$$ sapply(x, FUN) محاسبه روی مولفه‌ها لیست، بردار یا آرایه بردار یا ماتریس
$$tapply()$$ tapply(x, INDEX, FUN) محاسبه به تفکیک عامل لیست، بردار یا آرایه بردار
$$mapply()$$ mapply(FUN,x) محاسبه با پارامترهای برداری لیست و تابع لیست، ماتریس یا آرایه

اگر مطلب بالا برای شما مفید بوده، آموزش‌ها و مطالب زیر نیز که در زمینه به کار‌گیری آمار و محاسبات در نرم‌افزار R هستند، به شما پیشنهاد می‌شوند:

^^

بر اساس رای ۳ نفر
آیا این مطلب برای شما مفید بود؟
شما قبلا رای داده‌اید!
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
One thought on “توابع Apply در زبان برنامه نویسی R — راهنمای کاربردی

بسیار عالی بود . ممنون

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *