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

۱۳۵۵ بازدید
آخرین به‌روزرسانی: ۰۶ خرداد ۱۴۰۲
زمان مطالعه: ۱۴ دقیقه
توابع Apply در زبان برنامه نویسی R — راهنمای کاربردی

اغلب برای انجام کارها یا محاسبات یکسان بر روی ستون‌های یک مجموعه داده، از حلقه‌های تکرار یا 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 پارامتر اصلی و یک یا چند پارامتر اختیاری است که در ادامه با آن‌ها آشنا می‌شویم.

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

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

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

مثال

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

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

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

apply

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

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

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

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

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

مثال

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

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

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

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

1> m1 <- matrix(rnorm(30),nrow=5, ncol=6)
2
3> m1
4            [,1]       [,2]        [,3]       [,4]        [,5]       [,6]
5[1,] -2.05257158 -0.8285476  0.30718680  1.3715670 -0.74737752  1.1675458
6[2,]  1.87029090  1.1464947  1.74230249  0.3695404 -0.15568080  1.4076545
7[3,] -0.66610135 -0.9260289 -1.32928373 -0.1576227  1.40117008 -0.5165439
8[4,] -0.01877978 -0.7377102 -0.09684306 -0.1688754  2.90072436 -0.5722849
9[5,]  0.40026689  0.5550915  1.99883076  0.8466858 -0.01250701 -0.5998639
10
11> a_m1 <- apply(m1, 1,mean)
12
13> a_m2 <- apply(m1, 1,mean,trim=0.2)
14
15> a_m1
16[1] -0.1303662  1.0634337 -0.3657351  0.2177052  0.5314173
17
18> a_m2
19[1] -0.02529815  1.16649803 -0.56657421 -0.21419579  0.44738430
20>

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

apply function

تابع $$lapply()$$

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

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

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

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

1la_m1=lapply(m1,sq)
2la_m1
3unlistla_m1=unlist(la_m1)
4unlistla_m1

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

1> la_m1
2[[1]]
3[1] 1
4
5[[2]]
6[1] 4
7
8[[3]]
9[1] 9
10
11[[4]]
12[1] 16
13
14[[5]]
15[1] 25
16
17[[6]]
18[1] 36
19
20[[7]]
21[1] 49
22
23[[8]]
24[1] 64
25
26[[9]]
27[1] 81
28
29[[10]]
30[1] 100
31
32[[11]]
33[1] 1
34
35[[12]]
36[1] 4
37
38[[13]]
39[1] 9
40
41[[14]]
42[1] 16
43
44[[15]]
45[1] 25
46
47[[16]]
48[1] 36
49
50[[17]]
51[1] 49
52
53[[18]]
54[1] 64
55
56[[19]]
57[1] 81
58
59[[20]]
60[1] 100
61
62[[21]]
63[1] 1
64
65[[22]]
66[1] 4
67
68[[23]]
69[1] 9
70
71[[24]]
72[1] 16
73
74[[25]]
75[1] 25
76
77[[26]]
78[1] 36
79
80[[27]]
81[1] 49
82
83[[28]]
84[1] 64
85
86[[29]]
87[1] 81
88
89[[30]]
90[1] 100
91
92> unlistla_m1=unlist(la_m1)
93> unlistla_m1
94 [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
95>

مثال

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

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

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

1> pnames <- c("GOERGE","DAVID","CHARLS","FIGO")
2> pnames_lower <-lapply(pnames, tolower)
3> pnames_lower
4[[1]]
5[1] "goerge"
6
7[[2]]
8[1] "david"
9
10[[3]]
11[1] "charls"
12
13[[4]]
14[1] "figo"
15
16> str(pnames_lower)
17List of 4
18 $ : chr "goerge"
19 $ : chr "david"
20 $ : chr "charls"
21 $ : chr "figo"

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

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

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

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

تابع $$sapply()$$

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

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

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

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

1head(cars)
2dt <- cars
3lmn_cars <- lapply(dt, min)
4smn_cars <- sapply(dt, min)
5lmn_cars
6smn_cars

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

1> head(cars)
2  speed dist
31     4    2
42     4   10
53     7    4
64     7   22
75     8   16
86     9   10
9> dt <- cars
10> lmn_cars <- lapply(dt, min)
11> smn_cars <- sapply(dt, min)
12> lmn_cars
13$`speed`
14[1] 4
15
16$dist
17[1] 2
18
19> smn_cars
20speed  dist 
21    4     2 
22>

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

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

1head(cars)
2dt <- cars
3lmn_cars <- lapply(dt, min)
4smn_cars <- sapply(dt, min)
5lmn_cars
6smn_cars
7
8lmxcars <- lapply(dt, max)
9smxcars <- sapply(dt, max)
10lmxcars
11
12avg <- function(x) {  
13  ( min(x) + max(x) ) / 2}
14fcars <- sapply(dt, avg)
15fcars

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

1> dt <- cars
2> lmn_cars <- lapply(dt, min)
3> smn_cars <- sapply(dt, min)
4> lmn_cars
5$`speed`
6[1] 4
7
8$dist
9[1] 2
10
11> smn_cars
12speed  dist 
13    4     2 
14> 
15> lmxcars <- lapply(dt, max)
16> smxcars <- sapply(dt, max)
17> lmxcars
18$`speed`
19[1] 25
20
21$dist
22[1] 120
23
24> 
25> avg <- function(x) {  
26+   ( min(x) + max(x) ) / 2}
27> fcars <- sapply(dt, avg)
28> fcars
29speed  dist 
30 14.5  61.0 
31>

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

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

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

1below_ave <- function(x) {  
2    ave <- mean(x) 
3    return(x[x < ave])
4}

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

1dt_s<- sapply(dt, below_ave)
2dt_l<- lapply(dt, below_ave)
3dt_l
4dt_s
5identical(dt_s, dt_l)

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

1> dt_s<- sapply(dt, below_ave)
2> dt_l<- lapply(dt, below_ave)
3> dt_l
4$`speed`
5 [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
6
7$dist
8 [1]  46  60  80  54  50  56  76  84  46  68  48  52  56  64  66  54  70  92  93 120  85
9
10> dt_s
11$`speed`
12 [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
13
14$dist
15 [1]  46  60  80  54  50  56  76  84  46  68  48  52  56  64  66  54  70  92  93 120  85
16
17> identical(dt_s, dt_l)
18[1] TRUE

تابع $$tapply()$$

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

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

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

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

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

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

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

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

1> smp=sample(1:150,10)
2> iris[smp,]
3    Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
43            4.7         3.2          1.3         0.2     setosa
540           5.1         3.4          1.5         0.2     setosa
679           6.0         2.9          4.5         1.5 versicolor
7102          5.8         2.7          5.1         1.9  virginica
839           4.4         3.0          1.3         0.2     setosa
914           4.3         3.0          1.1         0.1     setosa
1074           6.1         2.8          4.7         1.2 versicolor
11129          6.4         2.8          5.6         2.1  virginica
12134          6.3         2.8          5.1         1.5  virginica
1369           6.2         2.2          4.5         1.5 versicolor
14> ts=tapply(iris$Sepal.Width, iris$Species, mean)
15> ts
16    setosa versicolor  virginica 
17     3.428      2.770      2.974 
18>

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

1# tsapply and compute mean of each column
2tsepw=tapply(iris$Sepal.Width, iris$Species, mean)
3tsepl=tapply(iris$Sepal.Length, iris$Species, mean)
4tpepw=tapply(iris$Petal.Width, iris$Species, mean)
5tpepl=tapply(iris$Petal.Length, iris$Species, mean)
6# Combined Output
7tslist=cbind(tsepw,tsepl,tpepw,tpepl)
8colnames(tslist) =c("Mean S.Width","Mean S.Length","Mean P.Width","Mean P.Length")
9# Showing Output
10tslist

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

1> tslist
2           Mean S.Width Mean S.Length Mean P.Width Mean P.Length
3setosa            3.428         5.006        0.246         1.462
4versicolor        2.770         5.936        1.326         4.260
5virginica         2.974         6.588        2.026         5.552
6>

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

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

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

مثال

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

1iris
2s=function(x) x=list(mean(x),median(x),sum(x))
3ts=tapply(iris$Sepal.Width, iris$Species, s)
4names(ts)=c("Mean ","Median ", "Sum" )
5tp=unlist(ts)
6ts
7names(tp)=c("Mean Setosa","Median Setosa", 
8"Sum Setosa","Mean versicolor","Median versicolor",
9 "Sum versicolor" ,"Mean virginica","Median virginica", "Sum virginica")
10tp

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

1> ts
2$`Mean `
3$`Mean `[[1]]
4[1] 3.428
5
6$`Mean `[[2]]
7[1] 3.4
8
9$`Mean `[[3]]
10[1] 171.4
11
12
13$`Median `
14$`Median `[[1]]
15[1] 2.77
16
17$`Median `[[2]]
18[1] 2.8
19
20$`Median `[[3]]
21[1] 138.5
22
23
24$Sum
25$Sum[[1]]
26[1] 2.974
27
28$Sum[[2]]
29[1] 3
30
31$Sum[[3]]
32[1] 148.7
33
34
35> names(tp)=c("Mean Setosa","Median Setosa", 
36+             "Sum Setosa","Mean versicolor","Median versicolor",
37+             "Sum versicolor" ,"Mean virginica","Median virginica", "Sum virginica")
38> tp
39      Mean Setosa     Median Setosa        Sum Setosa   Mean versicolor Median versicolor    Sum versicolor 
40            3.428             3.400           171.400             2.770             2.800           138.500 
41   Mean virginica  Median virginica     Sum virginica 
42            2.974             3.000           148.700 
43>

تابع $$mapply()$$

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

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

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

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

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

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

مثال

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

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

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

1> mp=mapply(rep,1:4,4)
2> mp
3     [,1] [,2] [,3] [,4]
4[1,]    1    2    3    4
5[2,]    1    2    3    4
6[3,]    1    2    3    4
7[4,]    1    2    3    4
8>

به این ترتیب تابع 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 هستند، به شما پیشنهاد می‌شوند:

^^

بر اساس رای ۴ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
۱ دیدگاه برای «توابع Apply در زبان برنامه نویسی R — راهنمای کاربردی»

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

نظر شما چیست؟

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