ट्रैक
/
R
R
/
अभ्यास
/
निर्देशांक रूपांतरण
निर्देशांक रूपांतरण

निर्देशांक रूपांतरण

सीखने का अभ्यास

परिचय

मूल बातें कॉन्सेप्ट में फंक्शन का परिचय दिया गया था, और उदाहरण कुछ इस तरह थे:

squareit <- function(x) {
    x * x
}

squareit(3)
#> [1] 9

# shorter form
squareit_short <- function(x) x ^ 2

squareit की परिभाषा को ध्यान से देखें, तो हम उसके अलग-अलग हिस्से पहचान सकते हैं:

  • यह फंक्शन एक औपचारिक आर्गुमेंट x लेता है।
  • इसके अंदर एक फंक्शन बॉडी होती है, जो आमतौर पर ब्रैकेट { } में लिखी जाती है।
  • फंक्शन ऑब्जेक्ट बन जाने के बाद उसे एक वेरिएबल squareit में असाइन किया जाता है।

R में फंक्शन एक फर्स्ट क्लास ऑब्जेक्ट है, बिल्कुल संख्याओं और स्ट्रिंग की तरह। इसलिए squareit <- function... भी एक असाइनमेंट है, जो लिखने के तरीके से बिल्कुल x <- 42 जैसा है।

आर्गुमेंट

Python और Julia जैसी दूसरी स्क्रिप्टिंग भाषाओं के उलट, R पोज़िशनल आर्गुमेंट और कीवर्ड आर्गुमेंट में कोई साफ़ अंतर नहीं करता।

फंक्शन कॉल में वैल्यू या तो पोज़िशन के अनुसार दी जा सकती हैं, या नाम लेकर। नाम लेकर देने का तरीका उन जटिल फंक्शन के लिए काम आता है जिनमें बहुत सारे आर्गुमेंट होते हैं और उनका क्रम याद रखना मुश्किल होता है।

f <- function(x, y) x / y

# call positionally
f(4, 2)
#> [1] 2

# call by name
f(y = 2, x = 4)
#> [1] 2

वैकल्पिक आर्गुमेंट

फंक्शन बनाते समय आर्गुमेंट की डिफ़ॉल्ट वैल्यू भी दी जा सकती है, लेकिन वह उन सभी आर्गुमेंट के बाद आनी चाहिए जिनकी कोई डिफ़ॉल्ट वैल्यू नहीं है।

इसके बाद हम चुन सकते हैं कि डिफ़ॉल्ट वैल्यू ही लें या उसे बदल दें।

g <- function(x, y = 2) x / y

# default y value
g(6)
#> [1] 3

# explicit y value
g(6, 3)
#> [1] 2

अतिरिक्त आर्गुमेंट

कितने भी अतिरिक्त आर्गुमेंट लेने के लिए फंक्शन की परिभाषा में ... (एलिप्सिस) लगाइए। फंक्शन कॉल में दी गई अतिरिक्त वैल्यू को वेक्टर में बदला जा सकता है, लेकिन इन "डॉट आर्ग्स" का दूसरा तरीका जानने के लिए आगे पढ़िए (कई दूसरी भाषाओं में इन्हें "वारआर्ग्स" कहा जाता है)।

var_f <- function(x, y, ...) {
  print(c(...))
}

var_f(2, 3, "opt1", "opt2")
#> [1] "opt1" "opt2"

फंक्शन एनवायरनमेंट

पहले हमने कहा था कि औपचारिक आर्गुमेंट और फंक्शन बॉडी, दोनों ही फंक्शन के हिस्से होते हैं।

असल में एक तीसरा हिस्सा भी होता है: वह एनवायरनमेंट जिसमें फंक्शन को बनाया जाता है।

इसे नेस्टेड फंक्शन के उदाहरण से समझा जा सकता है:

outer_func <- function(x) {
  inner_func <- function(y) {
    x * y
  }
  
  inner_func(3)
}

outer_func(5)
#> [1] 15

फंक्शन कॉल, आउटर फंक्शन को x = 5 भेजता है, और यह वैल्यू उस फंक्शन बॉडी के अंदर उपलब्ध रहती है।

इनर फंक्शन, आउटर फंक्शन की बॉडी का हिस्सा है, इसलिए उसके पास x की वैल्यू तक पहुँच होती है। दूसरे शब्दों में, x = 5 इनर फंक्शन के एनवायरनमेंट में है।

तकनीकी भाषा में इसे क्लोज़र कहते हैं।

डॉट आर्ग्स के साथ एनवायरनमेंट खास तौर पर अहम हो जाता है, क्योंकि इस तरह दी गई वैल्यू को फंक्शन बॉडी के अंदर होने वाले फंक्शन कॉल तक पहुँचाया जा सकता है। आउटर फंक्शन को यह जानने या परवाह करने की ज़रूरत नहीं होती कि डॉट आर्ग्स का मतलब क्या है।

f_var <- function(x, ...) {
  sum(x, ...)
}

x <- c(1, 2, NA, 6)

# for sum(), na.rm defaults to FALSE
f_var(x)
#> [1] NA

# pass through the na.rm value
f_var(x, na.rm = TRUE)
#> [1] 9

इस तरीके का इस्तेमाल Tidyverse की लाइब्रेरीज़, जैसे stringr, जमकर करती हैं। stringr के कई फंक्शन, stringi और बेस R के लो-लेवल फंक्शन के ऊपर एक यूज़र-फ्रेंडली रैपर हैं।

str_*() फंक्शन को दिए गए अतिरिक्त आर्गुमेंट सीधे उन्हीं लो-लेवल फंक्शन तक पहुँचा दिए जाते हैं।

रिटर्न वैल्यू के रूप में फंक्शन

कोई आउटर फंक्शन अपने अंदर एक इनर फंक्शन बना सकता है (नाम वाला या अनाम, जैसा नीचे बताया गया है) और उसे रिटर्न वैल्यू के तौर पर लौटा सकता है।

जो फंक्शन लौटाया जाता है, उसमें वह एनवायरनमेंट भी शामिल रहता है जिसमें उसे बनाया गया था।

times_y <- function(x) {
  # anonymous function - see next section
  \(y) x * y
}

f <- times_y(3)
class(f)
#> [1] "function"

f(4)
#> [1] 12

अनाम फंक्शन

जब हम कोई फंक्शन बनाते हैं, तो आमतौर पर बने हुए फंक्शन ऑब्जेक्ट को किसी वेरिएबल से जोड़ देते हैं।

x <- 2

# function definition captures x from the environment
doubleit_short <- function(y) x * y

इससे स्क्रिप्ट में बाद में उस फंक्शन का इस्तेमाल करना आसान हो जाता है, लेकिन ऐसा जोड़ना ज़रूरी नहीं है। जहाँ उसकी ज़रूरत पड़ी हो, वहीं एक बार इस्तेमाल होने वाला छोटा फंक्शन काम आ सकता है। बिना नाम जोड़े ऐसे फंक्शन को अनाम फंक्शन कहा जाता है।

अनाम फंक्शन इतने आम हैं कि (R v4.1.0 से) इन्हें बनाने के लिए एक छोटा तरीका भी है: function शब्द की जगह बैकस्लैश \ लिख दीजिए।

इसका एक उदाहरण पिछले हिस्से में, रिटर्न वैल्यू वाले भाग में दिया गया था।

बदलाव पर कॉपी

R वेक्टर के अलग-अलग एलिमेंट को वैल्यू असाइन करने देता है। अगर हम किसी फंक्शन को आर्गुमेंट के तौर पर एक वेक्टर देते हैं और उसे लौटाने से पहले फंक्शन बॉडी में बदल देते हैं, तो हमें बदला हुआ वेक्टर मिलता है।

लेकिन मूल वेक्टर का क्या हुआ?

f <- function(vec) {
  vec[1] <- 42
  vec
}

vals <- c(1, 3, 4)

# f() returns a modified vector
f(vals)
#> [1] 42  3  4  

# the original vector is unchanged.
vals
#> [1] 1 3 4

R एक ऐसी भाषा है जो डेटा साइंस के लिए बनाई गई है। ऐसा डेटा जमा करने में बहुत समय, मेहनत और कभी-कभी चौंका देने वाली रकम लग सकती है: इसलिए यह बहुत ज़रूरी है कि हम उसे खराब न करें!

आम तौर पर (कुछ अपवादों को छोड़कर) नीति बदलाव पर कॉपी की है। अगर किसी ऑब्जेक्ट (जैसे वेक्टर) को ऐसे तरीके से बदला जाए जिससे आगे दिक्कत हो सकती हो, तो R एक बदली हुई कॉपी लौटाता है और मूल को छेड़ता नहीं।

बड़े डेटा स्ट्रक्चर की कॉपी बनाना कंप्यूटर के लिए महँगा पड़ सकता है, लेकिन जब विकल्प डेटा के खराब हो जाने का हो, तो आम तौर पर यही कम नुकसानदेह रास्ता है।

Caution

ऐसी कार्रवाइयों से बचिए जिनमें एक ही डेटा की बार-बार कॉपी बनती हो।

इसका भाव, Tidyverse के लेखक Hadley Wickham के शब्दों में:

लूप अपने आप में धीमे नहीं होते, लेकिन उनके अंदर धीमी कार्रवाइयाँ डाल देना खतरनाक रूप से आसान हो जाता है।

वेक्टराइज़ेशन या हायर-ऑर्डर फंक्शन, इस तरह की परफॉर्मेंस खराब करने वाली समस्याओं से आपकी रक्षा कर सकते हैं।

निर्देश

आपकी कंपनी वेब पेज बनाने के लिए मैपिंग डेटा पर काम करती आ रही है। कुछ चर्चा के बाद यह तय किया गया कि कुछ गणनाएँ गतिशील रूप से करने के लिए R का इस्तेमाल शुरू किया जाए।

कोशिश कीजिए कि आप सीधे वेक्टर बिंदुओं के साथ ही काम करें: उन्हें अलग-अलग x और y निर्देशांकों में खोलने की ज़रूरत नहीं है।

1. निर्देशांक स्केल कीजिए

scale(point, s) फंक्शन बनाइए, जो किसी भी संख्या में आयामों वाला बिंदु लेता है और उसे पहले से तय s से स्केल करता है।

# A 2D example
s <- c(0.5, 0.8)
point <- c(10, 5)
scale(point, s)
#> [1] 5 4

2. निर्देशांक स्थानांतरित कीजिए

translate(point, ...) फंक्शन बनाइए, जो एक नया बिंदु लौटाता है जिसमें हर निर्देशांक डॉट-डॉट-डॉट में दी गई वैल्यू से स्थानांतरित हो जाता है।

यह फंक्शन किसी भी आयाम वाले बिंदुओं को संभाल सकना चाहिए, और डॉट-डॉट-डॉट के आर्गुमेंट की संख्या उस आयाम से मेल खानी चाहिए।

point2d <- c(2, 3)

# supply dx, dy translations
translate(point2d, 0.5, 0.6)
#> [1] 2.5 3.6

point3d <- c(2, 3, 4)

# supply dx, dy, dz translations
translate(point3d, 0.5, 0.6, 0.7)
#> [1] 2.5 3.6 4.7

3. 2D बिंदु का रूपांतरण कीजिए

आपके कुछ साथियों का R में अनुभव कम है, इसलिए आप {x, y} निर्देशांक जोड़ों के लिए दोबारा इस्तेमाल हो सकने वाले रूपांतरण बनाने के लिए फंक्शन क्लोज़र का इस्तेमाल करने का तय करते हैं।

transform2d(dx, dy, s) फंक्शन बनाइए, जो एक क्लोज़र का इस्तेमाल करके ऐसा फंक्शन लौटाता है जो किसी बिंदु का बार-बार किया जा सकने वाला 2D स्थानांतरण और स्केलिंग कर सके।

स्केलिंग वाला आर्गुमेंट s वैकल्पिक होना चाहिए, और उसकी डिफॉल्ट वैल्यू 1 होनी चाहिए।

लौटाया गया फंक्शन एक 2D बिंदु लेगा, और फिर:

  1. उसे पहले से तय dx और dy से स्थानांतरित करेगा
  2. उसे s से स्केल करेगा
# scale all coordinates by the same amount (2) in this example
# your code needs to be able to handle a vector `s`

tf2d <- transform2d(0.5, 0.6, 2)
class(tf2d)
#> [1] "function"

tf2d(c(2, 3))
#> [1] 5.0 7.2

# Accept default for `s`
tf2d <- transform2d(0.5, 0.6)

tf2d(c(2, 3))
#> 2.5 3.6

संक्रियाओं का क्रम ज़रूरी है: पहले स्थानांतरित करना और फिर स्केल करना सही परिणाम देता है, लेकिन पहले स्केल करना और फिर स्थानांतरित करना नहीं देता। तकनीकी भाषा में कहें तो ये संक्रियाएँ क्रम विनिमेय नहीं हैं।

4. 3D बिंदु का रूपांतरण कीजिए

मैपिंग डेटा में ऊँचाइयाँ भी हो सकती हैं, जिनका इस्तेमाल स्क्रीन पर शेडिंग और कंटूर जोड़ने के लिए किया जाता है।

transform3d(dx, dy, dz, s) फंक्शन बनाइए, जो एक फंक्शन लौटाता है।

स्केलिंग वाला आर्गुमेंट s वैकल्पिक होना चाहिए, और उसकी डिफॉल्ट वैल्यू 1 होनी चाहिए।

लौटाया गया फंक्शन एक 3D बिंदु लेगा, उसे पहले से तय वैल्यू से स्थानांतरित करेगा, और फिर उसे s से स्केल करेगा।

# using default `s`
tf3d <- transform3d(0.5, 0.6, 0.7)
point <- c(2, 3, 4)
tf3d(point)
#> [1] 2.5 3.6 4.7

# using vector `s`
tf3d <- transform3d(0.5, 0.6, 0.7, c(0.5, 0.6, 0.7))
tf3d(point)
#> [1] 1.25 2.16 3.29
GitHub के ज़रिए संपादित करें यह लिंक एक नई विंडो या टैब में खुलता है
R Exercism

निर्देशांक रूपांतरण शुरू करने के लिए तैयार हैं?

Exercism पर साइन अप कीजिए और R को 21 कॉन्सेप्ट111 अभ्यास तथा असली इंसानों से मिलने वाली मेंटरिंग के साथ सीखिए और उसमें महारत हासिल कीजिए, वह भी बिल्कुल मुफ्त।