Dplyr का उपयोग करके तालिका की हर पंक्ति में एक फ़ंक्शन लागू करना?


121

जब plyrमेरे साथ काम करते हैं तो अक्सर adplyस्केलर फ़ंक्शंस के लिए उपयोग करना उपयोगी होता है जो मुझे प्रत्येक पंक्ति में लागू करना पड़ता है।

जैसे

data(iris)
library(plyr)
head(
     adply(iris, 1, transform , Max.Len= max(Sepal.Length,Petal.Length))
    )
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species Max.Len
1          5.1         3.5          1.4         0.2  setosa     5.1
2          4.9         3.0          1.4         0.2  setosa     4.9
3          4.7         3.2          1.3         0.2  setosa     4.7
4          4.6         3.1          1.5         0.2  setosa     4.6
5          5.0         3.6          1.4         0.2  setosa     5.0
6          5.4         3.9          1.7         0.4  setosa     5.4

अब मैं dplyrअधिक उपयोग कर रहा हूं , मैं सोच रहा हूं कि क्या ऐसा करने के लिए कोई सुव्यवस्थित / प्राकृतिक तरीका है? जैसा कि यह नहीं है कि मुझे क्या चाहिए:

library(dplyr)
head(
     mutate(iris, Max.Len= max(Sepal.Length,Petal.Length))
    )
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species Max.Len
1          5.1         3.5          1.4         0.2  setosa     7.9
2          4.9         3.0          1.4         0.2  setosa     7.9
3          4.7         3.2          1.3         0.2  setosa     7.9
4          4.6         3.1          1.5         0.2  setosa     7.9
5          5.0         3.6          1.4         0.2  setosa     7.9
6          5.4         3.9          1.7         0.4  setosa     7.9

मैंने हाल ही में पूछा था कि क्या mdplydplyr में एक समान था , और हैडली ने सुझाव दिया कि वे कुछ के आधार पर शराब बना सकते हैं do। मुझे लगता है कि यह यहां भी काम करेगा।
बपतिस्मा

4
आखिरकार dplyr के पास कुछ ऐसा rowwise()होगा जो प्रत्येक अलग-अलग पंक्ति के आधार पर समूह करेगा
Hadley

@hadley thx, क्या इसे तब ही व्यवहार नहीं करना चाहिए adplyजब आप एक समूहीकरण का उपयोग नहीं करते हैं? जैसा कि इसके निकट एकीकृत कार्य को group_byनहीं बुलाया जाता हैsplit_by
स्टीफन हेंडरसन

@StephenHenderson नहीं, क्योंकि आपको संपूर्ण रूप से टेबल पर काम करने के लिए किसी तरह की आवश्यकता है।
हैडली

1
@HYYaDoing हाँ, लेकिन यह विधि सामान्य नहीं है। उदाहरण के लिए कोई सोरम, पीनियन या पामेडियन नहीं है।
स्टीफन हेंडरसन

जवाबों:


202

Dplyr 0.2 (मुझे लगता है) के रूप rowwise()में कार्यान्वित किया जाता है, इसलिए इस समस्या का उत्तर बन जाता है:

iris %>% 
  rowwise() %>% 
  mutate(Max.Len= max(Sepal.Length,Petal.Length))

गैर rowwiseवैकल्पिक

पांच साल (!) बाद में इस जवाब पर अभी भी बहुत अधिक ट्रैफ़िक आता है। चूंकि यह दिया गया था, इसलिए इसकी rowwiseसिफारिश नहीं की जाती है, हालांकि बहुत से लोग इसे सहज लगते हैं। अपने आप को एक एहसान करो और जेनी ब्रायन की पंक्ति-उन्मुख वर्कफ़्लोज़ के माध्यम से आर में जाएं, इस विषय पर एक अच्छा हैंडल पाने के लिए tidyverse सामग्री के साथ

सबसे सीधा तरीका जो मैंने पाया है वह हैडली के उदाहरणों में से एक पर आधारित है pmap:

iris %>% 
  mutate(Max.Len= purrr::pmap_dbl(list(Sepal.Length, Petal.Length), max))

इस दृष्टिकोण का उपयोग करके, आप फ़ंक्शन ( .f) के अंदर मनमाने ढंग से तर्क दे सकते हैं pmap

pmap एक अच्छा वैचारिक दृष्टिकोण है क्योंकि यह इस तथ्य को दर्शाता है कि जब आप पंक्ति वार ऑपरेशन कर रहे होते हैं तो आप वास्तव में वैक्टर (एक डेटाफ़्रेम में कॉलम) की सूची से ट्यूपल्स के साथ काम कर रहे होते हैं।


मैंने इसे (ऊपर से) आदर्श उत्तर में बदल दिया है क्योंकि मुझे लगता है कि यह इच्छित उपयोग है।
स्टीफन हेंडरसन

1
क्या गतिशील रूप से गठित डेटाफ़्रेम के मूल्यों को जोड़ना संभव है? तो इस डेटा फ्रेम में कॉलम नाम ज्ञात नहीं हैं। यदि कॉलम के नाम ज्ञात हैं, तो मैं जोड़ सकता हूं।
अरुण राजा

stackoverflow.com/questions/28807266/… अभी उत्तर मिला। इसमें वे राशि के बजाय सहसंबंध का उपयोग कर रहे हैं। लेकिन वही अवधारणा।
अरुण राजा

13
अगर यह काम नहीं करता है, तो सुनिश्चित करें कि आप वास्तव में dplyr का उपयोग कर रहे हैं :: उत्परिवर्ती plyr नहीं :: उत्परिवर्तित - मुझे पागल कर दिया
Jan-glx

धन्यवाद YAK, यह मुझे भी। यदि आप दोनों plyrऔर dplyrपैकेज शामिल करते हैं, तो आप लगभग निश्चित रूप से गलत का उपयोग कर रहे हैं mutateजब तक कि आप स्पष्ट रूप से गुंजाइश प्रदान नहीं करते हैं dplyr::mutate
क्रिस वॉर्थ

22

मुहावरेदार दृष्टिकोण एक उचित रूप से सदिश फ़ंक्शन बनाने के लिए होगा।

Rpmaxजो यहां उपयुक्त है, प्रदान करें, हालांकि यह आपको एक मनमाने ढंग से फ़ंक्शन के वेक्टरकृत मनमाने संस्करण बनाने की अनुमति देने के Vectorizeलिए एक आवरण के रूप में भी प्रदान करता है mapply

library(dplyr)
# use base R pmax (vectorized in C)
iris %>% mutate(max.len = pmax(Sepal.Length, Petal.Length))
# use vectorize to create your own function
# for example, a horribly inefficient get first non-Na value function
# a version that is not vectorized
coalesce <- function(a,b) {r <- c(a[1],b[1]); r[!is.na(r)][1]}
# a vectorized version
Coalesce <- Vectorize(coalesce, vectorize.args = c('a','b'))
# some example data
df <- data.frame(a = c(1:5,NA,7:10), b = c(1:3,NA,NA,6,NA,10:8))
df %>% mutate(ab =Coalesce(a,b))

ध्यान दें कि C / C ++ में वेक्टराइज़ेशन लागू करना तेज़ होगा, लेकिन ऐसा कोई magicPonyपैकेज नहीं है जो आपके लिए फ़ंक्शन लिखेगा।


thx, यह एक शानदार उत्तर है, जैसा कि आप कहते हैं, उत्कृष्ट सामान्य R शैली-शैली है, लेकिन मुझे नहीं लगता कि यह वास्तव में मेरे प्रश्न को संबोधित करता है कि क्या कोई dplyrरास्ता है ... क्योंकि यह dplyr के बिना सरल होगा जैसे with(df, Coalesce(a,b)), शायद, यह एक है हालांकि उत्तर की तरह - dplyrउस के लिए उपयोग नहीं करते ?
स्टीफन हेंडरसन

4
मानना ​​पड़ेगा कि मैंने चेक किया कि magicPonyपैकेज नहीं है । बहुत बुरा
rsoren

21

आपको पंक्ति द्वारा समूह बनाने की आवश्यकता है:

iris %>% group_by(1:n()) %>% mutate(Max.Len= max(Sepal.Length,Petal.Length))

यह क्या 1किया गया है adply


ऐसा लगता है कि एक सरल या "अच्छे" वाक्यविन्यास होना चाहिए।
स्टीफन हेंडरसन

@ स्टेफेनहेंडरसन, हो सकता है, मैं dplyrविशेषज्ञ नहीं हूं । उम्मीद है कि किसी और के साथ कुछ बेहतर होगा। नोट मैंने इसे थोड़ा साफ किया 1:n()
BrodieG

मुझे लगता है कि आप सही हैं, लेकिन मुझे लगता है कि बिना किसी ग्रुपिंग के डिफ़ॉल्ट व्यवहार की तरह व्यवहार होना चाहिए group_by(1:n())। अगर किसी के पास सुबह में कोई अन्य विचार नहीं है, तो मैं आपका टिकट काट दूंगा;)
स्टीफन हेंडरसन

इसके अलावा, ध्यान दें कि यह कुछ हद तक प्रलेखन के उल्लंघन में है n: "यह फ़ंक्शन प्रत्येक डेटा स्रोत के लिए विशेष रूप से कार्यान्वित किया जाता है और इसे केवल संक्षेप में उपयोग किया जा सकता है।", हालांकि यह काम करने लगता है।
BrodieG

क्या आप किसी तरह से अपने अनुक्रमांक द्वारा Sepal.Length और पेटल.लिफ्टिंग का उल्लेख कर सकते हैं? यदि आपके पास बहुत सारे चर हैं, तो यह आसान होगा। जैसे ... मैक्स.लेन = अधिकतम ([सी (1,3)])?
रासमस लार्सन

19

अपडेट 2017-08-03

यह लिखने के बाद, हेडली ने कुछ सामान फिर से बदल दिया। Purrr में जो फ़ंक्शंस होते थे, वे अब purrrlyr नामक एक नए मिश्रित पैकेज में हैं:

purrrlyr में कुछ कार्य होते हैं जो purrr और dplyr के चौराहे पर स्थित होते हैं। पैकेज को हल्का बनाने के लिए उन्हें purrr से हटा दिया गया है और क्योंकि उन्हें tidyverse में अन्य समाधानों द्वारा प्रतिस्थापित किया गया है।

तो, आपको उस कोड को काम के नीचे स्थापित करने के लिए उस पैकेज को + लोड करना होगा।

मूल पोस्ट

हैडली अक्सर अपने दिमाग में बदलाव करते हैं कि हमें क्या उपयोग करना चाहिए, लेकिन मुझे लगता है कि हमें पंक्ति कार्यक्षमता प्राप्त करने के लिए purrr में फ़ंक्शन पर स्विच करना चाहिए । कम से कम, वे एक ही कार्यक्षमता प्रदान करते हैं और लगभग उसी इंटरफ़ेस adplyसे होते हैं जैसे कि प्लाई से

दो संबंधित कार्य हैं, by_rowऔर invoke_rows। मेरी समझ यह है कि आप by_rowतब उपयोग करते हैं जब आप पंक्तियों पर लूप करना चाहते हैं और परिणाम को data.frame में जोड़ते हैं। invoke_rowsका उपयोग तब किया जाता है जब आप किसी डेटा.फ्रेम की पंक्तियों पर लूप करते हैं और फ़ंक्शन के तर्क के रूप में प्रत्येक कॉल को पास करते हैं। हम केवल पहले का उपयोग करेंगे।

उदाहरण

library(tidyverse)

iris %>% 
  by_row(..f = function(this_row) {
    browser()
  })

यह हमें इंटर्नल देखने देता है (इसलिए हम देख सकते हैं कि हम क्या कर रहे हैं), जो कि इसे करने के समान है adply

Called from: ..f(.d[[i]], ...)
Browse[1]> this_row
# A tibble: 1 × 5
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
         <dbl>       <dbl>        <dbl>       <dbl>  <fctr>
1          5.1         3.5          1.4         0.2  setosa
Browse[1]> Q

डिफ़ॉल्ट रूप से, by_rowआउटपुट के आधार पर एक सूची कॉलम जोड़ता है:

iris %>% 
  by_row(..f = function(this_row) {
      this_row[1:4] %>% unlist %>% mean
  })

देता है:

# A tibble: 150 × 6
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species      .out
          <dbl>       <dbl>        <dbl>       <dbl>  <fctr>    <list>
1           5.1         3.5          1.4         0.2  setosa <dbl [1]>
2           4.9         3.0          1.4         0.2  setosa <dbl [1]>
3           4.7         3.2          1.3         0.2  setosa <dbl [1]>
4           4.6         3.1          1.5         0.2  setosa <dbl [1]>
5           5.0         3.6          1.4         0.2  setosa <dbl [1]>
6           5.4         3.9          1.7         0.4  setosa <dbl [1]>
7           4.6         3.4          1.4         0.3  setosa <dbl [1]>
8           5.0         3.4          1.5         0.2  setosa <dbl [1]>
9           4.4         2.9          1.4         0.2  setosa <dbl [1]>
10          4.9         3.1          1.5         0.1  setosa <dbl [1]>
# ... with 140 more rows

यदि बदले में हम वापस आते हैं data.frame, तो हमें एक सूची मिलती है data.frame:

iris %>% 
  by_row( ..f = function(this_row) {
    data.frame(
      new_col_mean = this_row[1:4] %>% unlist %>% mean,
      new_col_median = this_row[1:4] %>% unlist %>% median
    )
  })

देता है:

# A tibble: 150 × 6
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species                 .out
          <dbl>       <dbl>        <dbl>       <dbl>  <fctr>               <list>
1           5.1         3.5          1.4         0.2  setosa <data.frame [1 × 2]>
2           4.9         3.0          1.4         0.2  setosa <data.frame [1 × 2]>
3           4.7         3.2          1.3         0.2  setosa <data.frame [1 × 2]>
4           4.6         3.1          1.5         0.2  setosa <data.frame [1 × 2]>
5           5.0         3.6          1.4         0.2  setosa <data.frame [1 × 2]>
6           5.4         3.9          1.7         0.4  setosa <data.frame [1 × 2]>
7           4.6         3.4          1.4         0.3  setosa <data.frame [1 × 2]>
8           5.0         3.4          1.5         0.2  setosa <data.frame [1 × 2]>
9           4.4         2.9          1.4         0.2  setosa <data.frame [1 × 2]>
10          4.9         3.1          1.5         0.1  setosa <data.frame [1 × 2]>
# ... with 140 more rows

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

iris %>% 
  by_row(.collate = "cols", ..f = function(this_row) {
    this_row[1:4] %>% unlist %>% mean
  })

iris %>% 
  by_row(.collate = "rows", ..f = function(this_row) {
    this_row[1:4] %>% unlist %>% mean
  })

दोनों का उत्पादन:

# A tibble: 150 × 6
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species  .out
          <dbl>       <dbl>        <dbl>       <dbl>  <fctr> <dbl>
1           5.1         3.5          1.4         0.2  setosa 2.550
2           4.9         3.0          1.4         0.2  setosa 2.375
3           4.7         3.2          1.3         0.2  setosa 2.350
4           4.6         3.1          1.5         0.2  setosa 2.350
5           5.0         3.6          1.4         0.2  setosa 2.550
6           5.4         3.9          1.7         0.4  setosa 2.850
7           4.6         3.4          1.4         0.3  setosa 2.425
8           5.0         3.4          1.5         0.2  setosa 2.525
9           4.4         2.9          1.4         0.2  setosa 2.225
10          4.9         3.1          1.5         0.1  setosa 2.400
# ... with 140 more rows

यदि हम 1 पंक्ति के साथ डेटा.फ्रेम का उत्पादन करते हैं, तो यह केवल उसी चीज के लिए महत्वपूर्ण है जिसका हम उपयोग करते हैं:

iris %>% 
  by_row(.collate = "cols", ..f = function(this_row) {
    data.frame(
      new_col_mean = this_row[1:4] %>% unlist %>% mean,
      new_col_median = this_row[1:4] %>% unlist %>% median
      )
  })

iris %>% 
  by_row(.collate = "rows", ..f = function(this_row) {
    data.frame(
      new_col_mean = this_row[1:4] %>% unlist %>% mean,
      new_col_median = this_row[1:4] %>% unlist %>% median
    )
  })

दोनों देते हैं:

# A tibble: 150 × 8
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species  .row new_col_mean new_col_median
          <dbl>       <dbl>        <dbl>       <dbl>  <fctr> <int>        <dbl>          <dbl>
1           5.1         3.5          1.4         0.2  setosa     1        2.550           2.45
2           4.9         3.0          1.4         0.2  setosa     2        2.375           2.20
3           4.7         3.2          1.3         0.2  setosa     3        2.350           2.25
4           4.6         3.1          1.5         0.2  setosa     4        2.350           2.30
5           5.0         3.6          1.4         0.2  setosa     5        2.550           2.50
6           5.4         3.9          1.7         0.4  setosa     6        2.850           2.80
7           4.6         3.4          1.4         0.3  setosa     7        2.425           2.40
8           5.0         3.4          1.5         0.2  setosa     8        2.525           2.45
9           4.4         2.9          1.4         0.2  setosa     9        2.225           2.15
10          4.9         3.1          1.5         0.1  setosa    10        2.400           2.30
# ... with 140 more rows

सिवाय इसके कि दूसरे में कॉल किया गया कॉलम है .rowऔर पहला नहीं है।

अंत में, यदि हमारा आउटपुट लंबाई 1 से या तो पंक्तियों के साथ vectorया data.frameसाथ है, तो यह मायने रखता है कि क्या हम पंक्तियों या कॉलनों का उपयोग करते हैं .collate:

mtcars[1:2] %>% by_row(function(x) 1:5)
mtcars[1:2] %>% by_row(function(x) 1:5, .collate = "rows")
mtcars[1:2] %>% by_row(function(x) 1:5, .collate = "cols")

क्रमशः, उत्पादन:

# A tibble: 32 × 3
     mpg   cyl      .out
   <dbl> <dbl>    <list>
1   21.0     6 <int [5]>
2   21.0     6 <int [5]>
3   22.8     4 <int [5]>
4   21.4     6 <int [5]>
5   18.7     8 <int [5]>
6   18.1     6 <int [5]>
7   14.3     8 <int [5]>
8   24.4     4 <int [5]>
9   22.8     4 <int [5]>
10  19.2     6 <int [5]>
# ... with 22 more rows

# A tibble: 160 × 4
     mpg   cyl  .row  .out
   <dbl> <dbl> <int> <int>
1     21     6     1     1
2     21     6     1     2
3     21     6     1     3
4     21     6     1     4
5     21     6     1     5
6     21     6     2     1
7     21     6     2     2
8     21     6     2     3
9     21     6     2     4
10    21     6     2     5
# ... with 150 more rows

# A tibble: 32 × 7
     mpg   cyl .out1 .out2 .out3 .out4 .out5
   <dbl> <dbl> <int> <int> <int> <int> <int>
1   21.0     6     1     2     3     4     5
2   21.0     6     1     2     3     4     5
3   22.8     4     1     2     3     4     5
4   21.4     6     1     2     3     4     5
5   18.7     8     1     2     3     4     5
6   18.1     6     1     2     3     4     5
7   14.3     8     1     2     3     4     5
8   24.4     4     1     2     3     4     5
9   22.8     4     1     2     3     4     5
10  19.2     6     1     2     3     4     5
# ... with 22 more rows

तो, नीचे की रेखा। यदि आप adply(.margins = 1, ...)कार्यक्षमता चाहते हैं, तो आप उपयोग कर सकते हैं by_row


2
by_rowपदावनत किया जाता है, यह कहते हुए कि "tidyr :: nest (); dplyr :: mutate (); purrr :: map ()" github.com/hadley/purrrlyr/bbb/…
momeara

वह बहुत सारे आर।
qwr

14

ब्रॉडी जी के जवाब का विस्तार करते हुए,

यदि फ़ंक्शन एक से अधिक पंक्ति में लौटता है, तो इसके बजाय mutate(), do()उपयोग किया जाना चाहिए। फिर इसे वापस एक साथ संयोजित करने के लिए, पैकेज rbind_all()से उपयोग करें dplyr

में dplyrसंस्करण dplyr_0.1.2का उपयोग कर 1:n()में group_by()खंड मेरे लिए काम नहीं करता। उम्मीद है हैडलीrowwise() जल्द ही लागू करेंगे

iris %>%
    group_by(1:nrow(iris)) %>%
    do(do_fn) %>%
    rbind_all()

प्रदर्शन का परीक्षण,

library(plyr)    # plyr_1.8.4.9000
library(dplyr)   # dplyr_0.8.0.9000
library(purrr)   # purrr_0.2.99.9000
library(microbenchmark)

d1_count <- 1000
d2_count <- 10

d1 <- data.frame(a=runif(d1_count))

do_fn <- function(row){data.frame(a=row$a, b=runif(d2_count))}
do_fn2 <- function(a){data.frame(a=a, b=runif(d2_count))}

op <- microbenchmark(
        plyr_version = plyr::adply(d1, 1, do_fn),
        dplyr_version = d1 %>%
            dplyr::group_by(1:nrow(d1)) %>%
            dplyr::do(do_fn(.)) %>%
            dplyr::bind_rows(),
        purrr_version = d1 %>% purrr::pmap_dfr(do_fn2),
        times=50)

इसके निम्न परिणाम हैं:

Unit: milliseconds
          expr       min        lq      mean    median        uq       max neval
  plyr_version 1227.2589 1275.1363 1317.3431 1293.5759 1314.4266 1616.5449    50
 dplyr_version  977.3025 1012.6340 1035.9436 1025.6267 1040.5882 1449.0978    50
 purrr_version  609.5790  629.7565  643.8498  644.2505  656.1959  686.8128    50

इससे पता चलता है कि नया purrrसंस्करण सबसे तेज है


1

कुछ इस तरह?

iris$Max.Len <- pmax(iris$Sepal.Length, iris$Petal.Length)

1
हाँ thx, यह एक बहुत ही विशिष्ट उत्तर है। लेकिन मेरा उदाहरण और प्रश्न dplyrकिसी भी स्केलर फ़ंक्शन के लिए सामान्य समाधान होने पर चिढ़ाने की कोशिश कर रहे हैं ।
स्टीफन हेंडरसन

सामान्य तौर पर, फ़ंक्शन को वेक्टर किया जाना चाहिए - यदि यह एक निराला कार्य है, तो आप लिख सकते हैं wacky.function <- function(col.1, col.2){...}, और फिर iris.wacky <- wacky.function(iris$Sepal.Length, iris$Petal.Length)
कोलकर्ल

अक्सर वे मुझे लगता है चाहिए, लेकिन मुझे लगता है कि जब आप की तरह कुछ का उपयोग कर रहे dplyrया plyrया कहते हैं कि data.tableआप अपने मुहावरे का उपयोग करने के लिए इतना है कि अपने कोड शैलियों की हिस्सेदारी मिश्रण करने के लिए एक मुश्किल नहीं बन जाता है की कोशिश करनी चाहिए। इसलिए सवाल।
स्टीफन हेंडरसन

plyrप्रलेखन की पहली पंक्ति है "प्लाई एक उपकरण का एक सेट है जो समस्याओं के एक सामान्य सेट को हल करता है: आपको प्रबंधनीय टुकड़ों में एक बड़ी समस्या को तोड़ने की जरूरत है, प्रत्येक टुकड़ों पर काम करें और फिर सभी टुकड़ों को वापस एक साथ रखें।" यह एक बहुत अलग समस्या की तरह लगता है जिसके लिए प्राथमिक स्तंभ संचालन सबसे अच्छा उपकरण है। यह भी समझा सकता है कि ऐसा करने के लिए कोई "प्राकृतिक" plyr/ dplyrकमांड क्यों नहीं है ।
कोकरोल

5
कसाई के लिए एक प्रसिद्ध उद्धरण: " यदि आपके पास एक प्लाई है तो आप इसे एक हथौड़ा और एक पेचकश के लिए भी इस्तेमाल करेंगे "
द लाटमेल
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.