डेटा में प्रत्येक निर्दिष्ट कॉलम में एक ही फ़ंक्शन कैसे लागू करें


85

मेरे पास एक data.table है जिसके साथ मैं कुछ कॉलम पर एक ही ऑपरेशन करना चाहूंगा। इन स्तंभों के नाम एक चरित्र वेक्टर में दिए गए हैं। इस विशेष उदाहरण में, मैं इन सभी स्तंभों को -1 से गुणा करना चाहूंगा।

कुछ खिलौना डेटा और एक वेक्टर प्रासंगिक कॉलम निर्दिष्ट करता है:

library(data.table)
dt <- data.table(a = 1:3, b = 1:3, d = 1:3)
cols <- c("a", "b")

अभी मैं इसे इस तरह कर रहा हूं, चरित्र वेक्टर पर लूपिंग:

for (col in 1:length(cols)) {
   dt[ , eval(parse(text = paste0(cols[col], ":=-1*", cols[col])))]
}

क्या लूप के बिना सीधे ऐसा करने का कोई तरीका है?

जवाबों:


150

यह काम करने लगता है:

dt[ , (cols) := lapply(.SD, "*", -1), .SDcols = cols]

परिणाम है

    a  b d
1: -1 -1 1
2: -2 -2 2
3: -3 -3 3

यहाँ कुछ तरकीबें हैं:

  • क्योंकि इसमें कोष्ठक हैं (cols) :=, परिणाम cols"कोल" नाम के कुछ नए चर के बजाय निर्दिष्ट कॉलम को सौंपा गया है ।
  • .SDcolsकॉल को बताता है कि हम केवल उन स्तंभों को देख रहे हैं, और हमें उन स्तंभों के साथ जुड़े ata .SDकी Subset का उपयोग करने की अनुमति देता है D
  • lapply(.SD, ...)पर काम करता है .SD, जो स्तंभों की एक सूची है (जैसे सभी data.frames और data.tables)। lapplyएक सूची देता है, इसलिए अंत में jऐसा लगता है cols := list(...)

संपादित करें : यहाँ एक और तरीका है जो संभवतः तेज़ है, जैसा कि @ अरुन ने उल्लेख किया है:

for (j in cols) set(dt, j = j, value = -dt[[j]])

21
एक और तरीका है setएक के साथ उपयोग करने के लिए for-loop। मुझे संदेह है कि यह तेज़ होगा।
अरुण

3
@ अरुन मैंने एक संपादन किया है। क्या आपका आशय यही था? मैंने setपहले इस्तेमाल नहीं किया है ।
फ्रैंक

8
+1 शानदार जवाब। हां, मैं इस तरह के मामलों के लिए एक forलूप पसंद करता हूं set
मैट डाउल

2
हां, set()मेरे डेटासेट के लिए ~ 4 गुना अधिक तेज़ उपयोग संभव है! गजब का।
कॉन्सटेंटिनो

2
धन्यवाद, @JamesHirschorn। मुझे यकीन नहीं है, लेकिन मुझे संदेह है कि उपयोग करने के बजाय उस तरह से कॉलम को सब्मिट करने के लिए अधिक ओवरहेड है, जो कि वैसे भी मानक मुहावरे हैं, जो इंट्रो विगनेट github.com/Rdatatable/data.table/Giki/Getting-started में दिखाई दे रहे हैं। मुहावरे के कारण का एक हिस्सा, मुझे लगता है, दो बार तालिका नाम टाइप करने से बचना है।
फ्रैंक

20

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

cols <- c("a", "b")
out_cols = paste("log", cols, sep = ".")
dt[, c(out_cols) := lapply(.SD, function(x){log(x = x, base = exp(1))}), .SDcols = cols]

1
क्या नियम के आधार पर नाम बदलने का कोई तरीका है? उदाहरण के लिए, आप% s> आईरिस%>% mutate_at (vars (माचिस ("सेपल")), सूची (times_two = ~। * 2)) कर सकते हैं और यह "_times_atwo" को नए नामों से जोड़ देगा।
केनीबी

1
मुझे नहीं लगता कि यह संभव है, लेकिन वास्तव में इसके बारे में निश्चित नहीं है।
hannes101

यह कॉलम नाम के साथ जोड़ देगा out_cols, जबकि अभी भी colsजगह छोड़ रहा है। श्रृंखला एक: तो, आप या तो स्पष्ट 1) केवल log.a और log.b के लिए पूछ रहा से उन को खत्म करने की आवश्यकता होगी [,.(outcols)]अंत और के लिए फिर से दुकान में dtके माध्यम से <-। 2) एक जंजीर के साथ पुराने स्तंभों को हटा दें [,c(cols):=NULL]। एक गैर-चेनिंग समाधान 3) dt[,c(cols):=...]उसके बाद हैsetnames(dt, cols, newcols)
एमपाग जूल

@mpag, हां यह सच है, लेकिन अनुभवजन्य अनुसंधान के मेरे उपयोग के मामले के लिए मुझे अधिकांश समय डेटा सेट में दोनों श्रृंखलाओं की आवश्यकता होती है।
hannes101

11

अद्यतन: बाद में लूप के बिना ऐसा करने का एक साफ तरीका है

dt[,(cols):= - dt[,..cols]]

यह आसान कोड पठनीयता के लिए एक साफ तरीका है। लेकिन जैसा कि प्रदर्शन के लिए यह नीचे के माइक्रोबैनचमार्क परिणाम के अनुसार फ्रैंक के समाधान के पीछे रहता है

mbm = microbenchmark(
  base = for (col in 1:length(cols)) {
    dt[ , eval(parse(text = paste0(cols[col], ":=-1*", cols[col])))]
  },
  franks_solution1 = dt[ , (cols) := lapply(.SD, "*", -1), .SDcols = cols],
  franks_solution2 =  for (j in cols) set(dt, j = j, value = -dt[[j]]),
  hannes_solution = dt[, c(out_cols) := lapply(.SD, function(x){log(x = x, base = exp(1))}), .SDcols = cols],
  orhans_solution = for (j in cols) dt[,(j):= -1 * dt[,  ..j]],
  orhans_solution2 = dt[,(cols):= - dt[,..cols]],
  times=1000
)
mbm

Unit: microseconds
expr                  min        lq      mean    median       uq       max neval
base_solution    3874.048 4184.4070 5205.8782 4452.5090 5127.586 69641.789  1000  
franks_solution1  313.846  349.1285  448.4770  379.8970  447.384  5654.149  1000    
franks_solution2 1500.306 1667.6910 2041.6134 1774.3580 1961.229  9723.070  1000    
hannes_solution   326.154  405.5385  561.8263  495.1795  576.000 12432.400  1000
orhans_solution  3747.690 4008.8175 5029.8333 4299.4840 4933.739 35025.202  1000  
orhans_solution2  752.000  831.5900 1061.6974  897.6405 1026.872  9913.018  1000

जैसा कि नीचे दिए गए चार्ट में दिखाया गया है

performance_comparison_chart

मेरा पिछला उत्तर: निम्नलिखित भी काम करता है

for (j in cols)
  dt[,(j):= -1 * dt[,  ..j]]

यह अनिवार्य रूप से डेढ़ साल पहले फ्रैंक के जवाब के समान है।
डीन मैकग्रेगर

1
धन्यवाद, फ्रैंक का जवाब सेट का उपयोग कर रहा था। जब मैं बड़ी डेटा के साथ काम करता हूं। जब मैं लाखों पंक्तियों के साथ काम करता हूं, तो मैं देखता हूं: = ऑपरेटर आउटपरफॉर्म कार्य करता है
Orhan Celik

2
जिस कारण से मैंने एक पुराने प्रश्न का उत्तर जोड़ा वह इस प्रकार है: मेरे पास भी इसी तरह का एक मुद्दा था, मैं इस पोस्ट पर Google खोज के साथ आया था। बाद में मुझे अपने मुद्दे का हल मिल गया, और मुझे लगता है कि यह यहाँ भी लागू होता है। वास्तव में मेरा सुझाव डेटा के एक नए फ़ंक्शन का उपयोग करता है। यह लाइब्रेरी के नए संस्करणों में उपलब्ध है, जो प्रश्न के समय में मौजूद नहीं था। मैंने सोचा कि यह साझा करने के लिए एक अच्छा विचार है, इसी तरह की समस्या वाले अन्य लोगों की सोच Google खोज के साथ समाप्त हो जाएगी।
ओरहान सेलिक

1
क्या आप dt3 पंक्तियों के साथ बेंचमार्किंग कर रहे हैं ?
Uwe

3
हेंस का जवाब एक अलग गणना कर रहा है और इसलिए दूसरों के साथ तुलना नहीं की जानी चाहिए, है ना?
फ्रैंक

2

उपरोक्त समाधानों में से कोई भी समूह द्वारा गणना के साथ काम नहीं करता है। निम्नलिखित मेरे लिए सबसे अच्छा है:

for(col in cols)
{
    DT[, (col) := scale(.SD[[col]], center = TRUE, scale = TRUE), g]
}

1

स्तंभों के स्ट्रिंग वेक्टर के आधार पर नए कॉलम बनाने के लिए उदाहरण जोड़ने के लिए। Jfly जवाब के आधार पर:

dt <- data.table(a = rnorm(1:100), b = rnorm(1:100), c = rnorm(1:100), g = c(rep(1:10, 10)))

col0 <- c("a", "b", "c")
col1 <- paste0("max.", col0)  

for(i in seq_along(col0)) {
  dt[, (col1[i]) := max(get(col0[i])), g]
}

dt[,.N, c("g", col1)]

0
library(data.table)
(dt <- data.table(a = 1:3, b = 1:3, d = 1:3))

Hence:

   a b d
1: 1 1 1
2: 2 2 2
3: 3 3 3

Whereas (dt*(-1)) yields:

    a  b  d
1: -1 -1 -1
2: -2 -2 -2
3: -3 -3 -3

1
शीर्षक में "हर निर्दिष्ट कॉलम" Fyi का मतलब था कि पूछने वाला इसे कॉलम के सबसेट में लागू करने में रुचि रखता था (शायद उन सभी में नहीं)।
फ्रैंक

1
@ यकीन पक्का! उस स्थिति में ओपी dt [, c ("a", "b")] * (- 1) कर सकता है।
आमांक

1
ठीक है, चलो पूरा करें और कहेंdt[, cols] <- dt[, cols] * (-1)
ग्रेग थॉमस

ऐसा लगता है कि आवश्यक नए वाक्यविन्यास को dt [, cols] <- dt [, ..col]] * (-1)
आर्थर यिप

0

dplyrफ़ंक्शंस data.tableएस पर काम करते हैं , इसलिए यहां एक dplyrसमाधान है जो "फॉर-लूप से बचा जाता है" :)

dt %>% mutate(across(all_of(cols), ~ -1 * .))

मैं Orhan के कोड (पंक्तियों और स्तंभों को जोड़ने) का उपयोग कर इसे बेंचमार्क और आप देखेंगे dplyr::mutateके साथ acrossज्यादातर धीमी अन्य समाधान की तुलना में सबसे तेजी से और data.table समाधान lapply का उपयोग करने से निष्पादित करता है।

library(data.table); library(dplyr)
dt <- data.table(a = 1:100000, b = 1:100000, d = 1:100000) %>% 
  mutate(a2 = a, a3 = a, a4 = a, a5 = a, a6 = a)
cols <- c("a", "b", "a2", "a3", "a4", "a5", "a6")

dt %>% mutate(across(all_of(cols), ~ -1 * .))
#>               a       b      d      a2      a3      a4      a5      a6
#>      1:      -1      -1      1      -1      -1      -1      -1      -1
#>      2:      -2      -2      2      -2      -2      -2      -2      -2
#>      3:      -3      -3      3      -3      -3      -3      -3      -3
#>      4:      -4      -4      4      -4      -4      -4      -4      -4
#>      5:      -5      -5      5      -5      -5      -5      -5      -5
#>     ---                                                               
#>  99996:  -99996  -99996  99996  -99996  -99996  -99996  -99996  -99996
#>  99997:  -99997  -99997  99997  -99997  -99997  -99997  -99997  -99997
#>  99998:  -99998  -99998  99998  -99998  -99998  -99998  -99998  -99998
#>  99999:  -99999  -99999  99999  -99999  -99999  -99999  -99999  -99999
#> 100000: -100000 -100000 100000 -100000 -100000 -100000 -100000 -100000

library(microbenchmark)
mbm = microbenchmark(
  base_with_forloop = for (col in 1:length(cols)) {
    dt[ , eval(parse(text = paste0(cols[col], ":=-1*", cols[col])))]
  },
  franks_soln1_w_lapply = dt[ , (cols) := lapply(.SD, "*", -1), .SDcols = cols],
  franks_soln2_w_forloop =  for (j in cols) set(dt, j = j, value = -dt[[j]]),
  orhans_soln_w_forloop = for (j in cols) dt[,(j):= -1 * dt[,  ..j]],
  orhans_soln2 = dt[,(cols):= - dt[,..cols]],
  dplyr_soln = (dt %>% mutate(across(all_of(cols), ~ -1 * .))),
  times=1000
)

library(ggplot2)
ggplot(mbm) +
  geom_violin(aes(x = expr, y = time)) +
  coord_flip()

रिप्रेक्स पैकेज (v0.3.0) द्वारा 2020-10-16 को बनाया गया

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.