اغلب برای انجام کارها یا محاسبات یکسان بر روی ستونهای یک مجموعه داده، از حلقههای تکرار یا loop استفاده میشود. به این منظور در زبان برنامهنویسی R خانواده توابع apply مانند apply(),lapply(),sapply(),mapply() و tapply()() وجود دارند که این عملیات تکراری را به واسطه محاسبات ماتریسی انجام میدهند، در نتیجه سرعت بالا و انعطاف زیادی دارند.
برای محاسبه یک تابع روی ماتریس یا آرایه میتوان از تابع 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
[,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
> a_m1 <- apply(m1, 2, sum)
> a_m1
[1] 15 40 15 40 15 40
نکته: از آنجایی که ماتریس دارای ۳۰ درایه است و فقط مقدارهای ۱ تا ۱۰ باید در آن قرار گیرند، نرمافزار با تکرار اعداد ۱ تا ۱۰ ماتریس را به ترتیب ستونها، کامل کرده است.
مثال 2 :
نتیجه اجرای تابع 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)
#output
> 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
#output
> 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، تابع 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.
مثال 1:
همانطور که دیده میشود، در این تابع پارامتر MARGIN احتیاجی نیست و تابع مورد نظر روی همه درایهها اعمال خواهد شد. با توجه به مثال قبل فرض کنید تابع sq را میخواهیم برای همه عناصر ماتریس به کار ببریم. کافی است با تابع lapply() مطابق با کد زیر محاسبات را انجام دهیم. نتیجه درست به مانند حالت قبل خواهد شد با این تفاوت که ماهیت خروجی در اینجا یک لیست است.
البته در انتهای کد، متغیر حاصل از اجرای تابع lapply() را به کمک تابع unlist() از حالت لیست خارج کردهایم.
la_m1=lapply(m1,sq)
la_m1
unlistla_m1=unlist(la_m1)
unlistla_m1
> 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() این کار به راحتی امکانپذیر است.
به منظور تبدیل این لیست به یک بردار متنی، از تابع unlist() استفاده خواهیم کرد. به این ترتیب خواهیم داشت:
pnames <- c("GOERGE","DAVID","CHARLS","FIGO")
pnames_lower <-unlist(lapply(pnames,tolower))
str(pnames_lower)
#output
> pnames_lower <-unlist(lapply(pnames,tolower))
> str(pnames_lower)
chr [1:4] "goerge" "david" "charls" "figo"
اگر میخواهید خروجی محاسبات به صورت یک بردار درآید از تابع sapply() به جای lapply() استفاده کنید.
sapply(X, FUN)
Arguments:
-X: A vector or an object
-FUN: Function applied to each element of x
...: optional arguments to FUN.
مثال1:
فرض کنید که میخواهیم برای مجموعه دادههای 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
>
مثال 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
تا اینجا مشخص شد که محاسبات روی سطر، ستون و یا درایههای یک ماتریس یا چارچوب داده، توسط توابع 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
>
نسخه چند متغیره تابع lapply() را میتوان تابع mapply() در نظر گرفت. به این ترتیب میتوان پارامترها را به صورت برداری، معرفی کرد.
فرض کنید براساس مثال قبل لازم است که میانگین، میانه و جمع هر یک از متغیرهای مربوط به دادههای iris (بدون در نظر گرفتن ستون آخر که نوع گل را نشان میدهد) را محاسبه کنیم. برای این کار از کد زیر کمک میگیریم و برنامه را با کمترین میزان دستورات اجرا میکنیم. کدها را با مثال قبل مقایسه کنید.
mp=mapply(s,iris[,1:4])
mp
> 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
خلاصه نکات این صفحه
براساس ویژگیها و شیوه محاسبه هر یک از اعضای خانواده توابع apply جدول زیر تهیه شده است تا بهتر عملکرد هر یک از توابع مشخص و با دیگر توابع این گروه مقایسه شود.
دوره های آموزشی ما برای کنکور ارشد و دکتری رشته های روانشناسی، مشاوره، علوم تربیتی، پرستاری, مدیریت آموزشی و علوم شناختی کاربرد دارد.