मैं अपना सर्वश्रेष्ठ मार्गदर्शक देने की कोशिश करूंगा लेकिन यह आसान नहीं है क्योंकि किसी को {data.table}, {dplyr}, {dtplyr} और साथ ही {base R} सभी से परिचित होना होगा। मैं {data.table} और कई {tidy-world} पैकेज ({dplyr} को छोड़कर) का उपयोग करता हूं। दोनों से प्यार, हालांकि मैं dplyr करने के लिए data.table का वाक्यविन्यास पसंद करता हूं। मुझे आशा है कि सभी सुव्यवस्थित संकुल जब भी आवश्यक होंगे {dtplyr} या {data.table} बैकएंड के रूप में उपयोग करेंगे।
किसी भी अन्य अनुवाद के साथ (जैसा कि dplyr-to-sparkly / SQL), ऐसी चीजें हैं जिनका अनुवाद किया जा सकता है या नहीं, कम से कम अब तक। मेरा मतलब है, शायद एक दिन {dtplyr} इसे 100% अनुवादित कर सकता है, कौन जानता है। नीचे दी गई सूची संपूर्ण नहीं है और न ही यह 100% सही है क्योंकि मैं संबंधित विषयों / पैकेजों / मुद्दों आदि पर अपने ज्ञान के आधार पर उत्तर देने की पूरी कोशिश करूंगा।
महत्वपूर्ण रूप से, उन उत्तरों के लिए जो पूरी तरह से सही नहीं हैं, मुझे आशा है कि यह आपको कुछ दिशानिर्देश देता है कि {data.table} के किन पहलुओं पर आपको ध्यान देना चाहिए और, इसकी तुलना {dtplyr} से करें और खुद ही उत्तरों का पता लगाएं। इन जवाबों के लिए अनुमति न लें।
और, मुझे आशा है कि इस पोस्ट का उपयोग सभी {dplyr}, {data.table} या {dtplyr} उपयोगकर्ताओं / रचनाकारों के लिए चर्चा और सहयोग के लिए किया जा सकता है और #RStats को और भी बेहतर बनाया जा सकता है।
{data.table} का उपयोग न केवल तेज और मेमोरी कुशल संचालन के लिए किया जाता है। कई लोग हैं, जिनमें स्वयं भी शामिल हैं, {data.table} के सुरुचिपूर्ण सिंटैक्स पसंद करते हैं। इसमें frollapply
सी में लिखे गए टाइम-सीरीज़ फ़ंक्शंस जैसे रोलिंग-फ़ैमिली (यानी ) जैसे अन्य तेज़ संचालन भी शामिल हैं । इसका उपयोग किसी भी फ़ंक्शंस के साथ किया जा सकता है, जिसमें tidyverse भी शामिल है। मैं {data.table} + {purrr} का बहुत उपयोग करता हूं!
संचालन की जटिलता
इसका आसानी से अनुवाद किया जा सकता है
library(data.table)
library(dplyr)
library(flights)
data <- data.table(diamonds)
# dplyr
diamonds %>%
filter(cut != "Fair") %>%
group_by(cut) %>%
summarize(
avg_price = mean(price),
median_price = as.numeric(median(price)),
count = n()
) %>%
arrange(desc(count))
# data.table
data [
][cut != 'Fair', by = cut, .(
avg_price = mean(price),
median_price = as.numeric(median(price)),
count = .N
)
][order( - count)]
{data.table} बहुत तेज़ और मेमोरी कुशल है क्योंकि (लगभग?) सब कुछ जमीन से C से अपडेट-बाय-रेफरेंस , कुंजी (थिंक एसक्यूएल) की प्रमुख अवधारणाओं के साथ बनाया गया है , और पैकेज में हर जगह उनका अथक अनुकूलन है। (यानी fifelse
, fread/fread
बेस आर द्वारा अपनाया गया मूलांक क्रम), जबकि सिंटैक्स संक्षिप्त और सुसंगत है, इसलिए मुझे लगता है कि यह सुरुचिपूर्ण है।
से परिचय data.table लिए , इस तरह के रूप में मुख्य डेटा हेरफेर आपरेशनों सबसेट, समूह, अद्यतन, में शामिल होने, आदि के लिए एक साथ रखा जाता है
संक्षिप्त और सुसंगत वाक्य रचना ...
प्रत्येक ऑपरेशन को मैप करने के संज्ञानात्मक बोझ के बिना द्रव का विश्लेषण करना ...
स्वचालित रूप से प्रत्येक ऑपरेशन के लिए आवश्यक डेटा को जानने के द्वारा आंतरिक रूप से और बहुत प्रभावी ढंग से स्वचालित रूप से अनुकूलन करना, बहुत तेज और मेमोरी कुशल कोड के लिए अग्रणी है
अंतिम बिंदु, उदाहरण के लिए,
# Calculate the average arrival and departure delay for all flights with “JFK” as the origin airport in the month of June.
flights[origin == 'JFK' & month == 6L,
.(m_arr = mean(arr_delay), m_dep = mean(dep_delay))]
हम पहली बार इनसेट में मिलते हैं जो कि मैचिंग पंक्ति सूचकांकों को खोजने के लिए है जहां मूल हवाई अड्डे "JFK" के बराबर है, और महीने 6L के बराबर है। हम अभी तक उन पंक्तियों के अनुसार पूरे डेटाटेबल को सब्मिट नहीं करते हैं।
अब, हम j को देखते हैं और पाते हैं कि यह केवल दो कॉलम का उपयोग करता है। और हमें जो करना है, उनका मतलब () की गणना करना है। इसलिए हम मिलान पंक्तियों के अनुरूप केवल उन कॉलमों को सब्मिट करते हैं, और उनके माध्य () की गणना करते हैं।
क्योंकि क्वेरी के तीन मुख्य घटक (i, j और by) एक साथ हैं [...] , data.table तीनों को देख सकते हैं और मूल्यांकन से पहले क्वेरी को पूरी तरह से अनुकूलित कर सकते हैं, प्रत्येक अलग से नहीं । इसलिए हम गति और मेमोरी दक्षता दोनों के लिए संपूर्ण उप सबसेट (यानी, arr_delay और dep_delay के अलावा कॉलम को हटाते हुए) से बचने में सक्षम हैं।
यह देखते हुए कि, {data.table} के लाभों को पुनः प्राप्त करने के लिए, {dtplr} का अनुवाद उस संदर्भ में सही होना चाहिए। संचालन जितना जटिल होगा, अनुवाद उतना ही कठिन होगा। ऊपर जैसे सरल ऑपरेशन के लिए, यह निश्चित रूप से आसानी से अनुवादित किया जा सकता है। जटिल लोगों के लिए, या {dtplyr} द्वारा समर्थित नहीं हैं, आपको अपने आप को ऊपर बताए अनुसार ढूंढना होगा, अनुवादित वाक्यविन्यास और बेंचमार्क की तुलना करने और परिचित संबंधित पैकेजों की तुलना करने के लिए।
जटिल संचालन या असमर्थित संचालन के लिए, मैं नीचे कुछ उदाहरण प्रदान करने में सक्षम हो सकता हूं। फिर, मैं बस अपनी पूरी कोशिश कर रहा हूं। मुझ पर कोमल बनो।
अद्यतन-दर-संदर्भ
मैं इंट्रो / विवरण में नहीं जाऊंगा लेकिन यहां कुछ लिंक दिए गए हैं
मुख्य संसाधन: संदर्भ शब्दार्थ
अधिक विवरण: जब डेटाटेबल बिल्कुल समझ में आता है, तो किसी अन्य डेटा का संदर्भ (बनाम कॉपी)
मेरी राय में, अपडेट-बाय-रेफरेंस , {data.table} की सबसे महत्वपूर्ण विशेषता है और यही इसे इतनी तेज और मेमोरी कुशल बनाती है। dplyr::mutate
डिफ़ॉल्ट रूप से इसका समर्थन नहीं करता है। जैसा कि मैं {dtplyr} से परिचित नहीं हूं, मुझे यकीन नहीं है कि {dtplyr} द्वारा कितना और क्या संचालन किया जा सकता है या इसका समर्थन नहीं किया जा सकता है। जैसा कि ऊपर उल्लेख किया गया है, यह संचालन की जटिलता पर भी निर्भर करता है, जो अनुवादों को प्रभावित करता है।
{Data.table} में अद्यतन-दर-संदर्भ का उपयोग करने के दो तरीके हैं
{data.table} का असाइनमेंट ऑपरेटर :=
set
-family: set
, setnames
, setcolorder
, setkey
, setDT
, fsetdiff
, और कई और अधिक
:=
की तुलना में अधिक सामान्यतः उपयोग किया जाता है set
। जटिल और बड़े डेटासेट के लिए, अद्यतन-दर-संदर्भ शीर्ष गति और मेमोरी दक्षता प्राप्त करने की कुंजी है। सोचने का आसान तरीका (100% सटीक नहीं है, क्योंकि विवरण इससे कहीं अधिक जटिल हैं क्योंकि इसमें हार्ड / उथले कॉपी और कई अन्य कारक शामिल हैं), कहते हैं कि आप 10GB के बड़े डेटासेट के साथ काम कर रहे हैं, जिसमें 10 कॉलम और 1GB हैं । एक कॉलम में हेरफेर करने के लिए, आपको केवल 1GB से निपटने की आवश्यकता है।
मुख्य बिंदु यह है कि अद्यतन-दर-संदर्भ के साथ , आपको केवल आवश्यक डेटा से निपटना होगा। इसीलिए {data.table} का उपयोग करते समय, विशेष रूप से बड़े डेटासेट के साथ काम करते हुए, हम जब भी संभव हो अपडेट-बाय-संदर्भ का उपयोग करते हैं। उदाहरण के लिए, बड़े मॉडलिंग डाटासेट में हेरफेर
# Manipulating list columns
df <- purrr::map_dfr(1:1e5, ~ iris)
dt <- data.table(df)
# data.table
dt [,
by = Species, .(data = .( .SD )) ][, # `.(` shorthand for `list`
model := map(data, ~ lm(Sepal.Length ~ Sepal.Width, data = . )) ][,
summary := map(model, summary) ][,
plot := map(data, ~ ggplot( . , aes(Sepal.Length, Sepal.Width)) +
geom_point())]
# dplyr
df %>%
group_by(Species) %>%
nest() %>%
mutate(
model = map(data, ~ lm(Sepal.Length ~ Sepal.Width, data = . )),
summary = map(model, summary),
plot = map(data, ~ ggplot( . , aes(Sepal.Length, Sepal.Width)) +
geom_point())
)
घोंसले के संचालन का list(.SD)
समर्थन {dtlyr} द्वारा नहीं किया जा सकता है जैसा कि उपयोगकर्ता द्वारा उपयोग किया जाता है tidyr::nest
? इसलिए मुझे यकीन नहीं है कि यदि बाद के ऑपरेशनों को {data.table} के रूप में अनुवादित किया जा सकता है तो तेज और कम मेमोरी है।
नोट: data.table का परिणाम "मिलीसेकंड" में है, "मिनट" में dplyr
df <- purrr::map_dfr(1:1e5, ~ iris)
dt <- copy(data.table(df))
bench::mark(
check = FALSE,
dt[, by = Species, .(data = list(.SD))],
df %>% group_by(Species) %>% nest()
)
# # A tibble: 2 x 13
# expression min median `itr/sec` mem_alloc `gc/sec` n_itr n_gc
# <bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl> <int> <dbl>
# 1 dt[, by = Species, .(data = list(.SD))] 361.94ms 402.04ms 2.49 705.8MB 1.24 2 1
# 2 df %>% group_by(Species) %>% nest() 6.85m 6.85m 0.00243 1.4GB 2.28 1 937
# # ... with 5 more variables: total_time <bch:tm>, result <list>, memory <list>, time <list>,
# # gc <list>
अद्यतन-दर-संदर्भ और यहां तक कि {data.table} के कई उपयोग-मामले हैं, उपयोगकर्ता हर समय इसके उन्नत संस्करण का उपयोग नहीं करेंगे क्योंकि इसके लिए अधिक कोड की आवश्यकता होती है। क्या {dtplyr} इन आउट-ऑफ-द-बॉक्स का समर्थन करते हैं, आपको स्वयं पता लगाना होगा।
एक ही फ़ंक्शन के लिए एकाधिक अद्यतन-दर-संदर्भ
मुख्य संसाधन: सुरुचिपूर्ण ढंग से डेटा में कई कॉलम असाइन करना। lapply () के साथ
इसमें या तो अधिक इस्तेमाल किया जाता है :=
या set
।
dt <- data.table( matrix(runif(10000), nrow = 100) )
# A few variants
for (col in paste0('V', 20:100))
set(dt, j = col, value = sqrt(get(col)))
for (col in paste0('V', 20:100))
dt[, (col) := sqrt(get(col))]
# I prefer `purrr::map` to `for`
library(purrr)
map(paste0('V', 20:100), ~ dt[, (.) := sqrt(get(.))])
{Data.table} मैट डॉवेल के निर्माता के अनुसार
(ध्यान दें कि बड़ी संख्या में स्तंभों की तुलना में बड़ी संख्या में लूप सेट करना अधिक सामान्य हो सकता है।)
Join + setkey + update-by-reference
मुझे अपेक्षाकृत बड़े डेटा के साथ तेजी से जुड़ने की जरूरत थी और हाल ही में इसी तरह के पैटर्न से जुड़ते हैं, इसलिए मैं सामान्य जोड़ के बजाय अपडेट-बाय-रेफरेंस की शक्ति का उपयोग करता हूं । जैसा कि उन्हें अधिक कोड की आवश्यकता होती है, मैं उन्हें पुन: प्रयोज्य और पठनीयता के लिए गैर-मानक मूल्यांकन के साथ निजी पैकेज में लपेटता हूं जहां मैं इसे कहता हूं setjoin
।
मैंने यहाँ कुछ बेंचमार्क किया: data.table join + update-by-reference + setkey
सारांश
# For brevity, only the codes for join-operation are shown here. Please refer to the link for details
# Normal_join
x <- y[x, on = 'a']
# update_by_reference
x_2[y_2, on = 'a', c := c]
# setkey_n_update
setkey(x_3, a) [ setkey(y_3, a), on = 'a', c := c ]
नोट: dplyr::left_join
यह भी परीक्षण किया गया था और यह ~ साथ धीमी 9,000 एमएस है, दोनों {data.table} की तुलना में अधिक स्मृति का उपयोग के update_by_reference
और setkey_n_update
, लेकिन {data.table} से कम स्मृति का उपयोग के normal_join। इसमें लगभग ~ 2.0GB मेमोरी की खपत हुई। मैंने इसे शामिल नहीं किया क्योंकि मैं केवल {data.table} पर ध्यान केंद्रित करना चाहता हूं।
मुख्य निष्कर्ष
setkey + update
और क्रमशः update
~ 11 और ~ 6.5 गुना अधिक तेज normal join
हैं
- पहले में शामिल होने पर, के प्रदर्शन
setkey + update
के समान है update
के रूप में की भूमि के ऊपर setkey
मोटे तौर पर अपने स्वयं के निष्पादन लाभ ऑफसेट
- दूसरे और बाद में मिलती है पर, के रूप में
setkey
की आवश्यकता नहीं है, setkey + update
तेजी से है update
(तेज़ या से ~ 1.8 गुना की normal join
~ 11 बार से)
उदाहरण
प्रदर्शनकारी और स्मृति कुशल जोड़ के लिए, update
या तो उपयोग करें या setkey + update
, जहां बाद अधिक कोड की कीमत पर तेज है।
आइए देखें कुछ छद्म कोड, संक्षिप्तता के लिए। लॉजिक्स समान हैं।
एक या कुछ स्तंभों के लिए
a <- data.table(x = ..., y = ..., z = ..., ...)
b <- data.table(x = ..., y = ..., z = ..., ...)
# `update`
a[b, on = .(x), y := y]
a[b, on = .(x), `:=` (y = y, z = z, ...)]
# `setkey + update`
setkey(a, x) [ setkey(b, x), on = .(x), y := y ]
setkey(a, x) [ setkey(b, x), on = .(x), `:=` (y = y, z = z, ...) ]
कई कॉलम के लिए
cols <- c('x', 'y', ...)
# `update`
a[b, on = .(x), (cols) := mget( paste0('i.', cols) )]
# `setkey + update`
setkey(a, x) [ setkey(b, x), on = .(x), (cols) := mget( paste0('i.', cols) ) ]
तेज और स्मृति के लिए आवरण कुशल जुड़ता है ... उनमें से कई ... समान जुड़ाव-पैटर्न के साथ, उन्हें setjoin
ऊपर की तरह लपेटें - साथ update
- बिना या बिनाsetkey
setjoin(a, b, on = ...) # join all columns
setjoin(a, b, on = ..., select = c('columns_to_be_included', ...))
setjoin(a, b, on = ..., drop = c('columns_to_be_excluded', ...))
# With that, you can even use it with `magrittr` pipe
a %>%
setjoin(...) %>%
setjoin(...)
के साथ setkey
, तर्क on
छोड़ा जा सकता है। यह पठनीयता के लिए भी शामिल किया जा सकता है, विशेष रूप से दूसरों के साथ सहयोग करने के लिए।
बड़ी पंक्ति-संचालन
- जैसा कि ऊपर बताया गया है, उपयोग करें
set
- अपनी तालिका को पूर्व-आबाद करें, अद्यतन-दर-संदर्भ तकनीकों का उपयोग करें
- सबसेट का उपयोग कर (यानी
setkey
)
संबंधित संसाधन: किसी data.table ऑब्जेक्ट के अंत में संदर्भ द्वारा एक पंक्ति जोड़ें
अद्यतन-दर-संदर्भ का सारांश
ये अद्यतन-दर-संदर्भ के कुछ उपयोग के मामले हैं । और भी कई हैं।
जैसा कि आप देख सकते हैं, बड़े डेटा से निपटने के उन्नत उपयोग के लिए, बड़े डेटासेट के लिए अपडेट-बाय-रेफरेंस का उपयोग करते हुए कई उपयोग के मामले और तकनीकें हैं । {Data.table} में उपयोग करना इतना आसान नहीं है और क्या {dtplyr} इसका समर्थन करता है, आप स्वयं पता लगा सकते हैं।
मैं इस पोस्ट में अद्यतन-दर-संदर्भ पर ध्यान केंद्रित करता हूं क्योंकि मुझे लगता है कि यह तेज़ और मेमोरी कुशल संचालन के लिए {data.table} की सबसे शक्तिशाली विशेषता है। उस ने कहा, कई, कई अन्य पहलू हैं जो इसे बहुत कुशल बनाते हैं और मुझे लगता है कि मूल रूप से {dtplyr} द्वारा समर्थित नहीं हैं।
अन्य प्रमुख पहलू
क्या / समर्थित नहीं है, यह भी संचालन की जटिलता पर निर्भर करता है और इसमें डेटा-टेबल की मूल विशेषता जैसे अद्यतन-दर-संदर्भ या शामिल है setkey
। और क्या अनुवादित कोड अधिक कुशल है (एक है जो data.table उपयोगकर्ता लिखेंगे) एक अन्य कारक भी है (यानी कोड का अनुवाद किया गया है, लेकिन क्या यह कुशल-संस्करण है?)। बहुत सी चीजें आपस में जुड़ी हुई हैं।
setkey
। देखें कुंजी और तेजी से बाइनरी खोज आधारित सबसेट
- माध्यमिक सूचकांक और ऑटो इंडेक्सिंग
- डेटा विश्लेषण के लिए .SD का उपयोग करना
- समय-श्रृंखला कार्य: सोचते हैं
frollapply
। रोलिंग फ़ंक्शंस, रोलिंग एग्रीगेट्स, स्लाइडिंग विंडो, मूविंग एवरेज
- रोलिंग जॉइन , नॉन-इक्वी जॉइन , (कुछ) "क्रॉस" जॉइन
- {data.table} ने नींव को गति और मेमोरी दक्षता में बनाया है, भविष्य में, यह कई कार्यों को शामिल करने का विस्तार कर सकता है (जैसे कि वे ऊपर बताए गए समय-श्रृंखला कार्यों को कैसे लागू करते हैं)
- सामान्य रूप में, data.table के बारे में अधिक जटिल आपरेशनों
i
, j
या by
परिचालन (तुम वहाँ में लगभग किसी भी एक्सप्रेशन का उपयोग कर सकते हैं), मुझे लगता है कि कठिन अनुवाद है, खासकर जब यह के साथ गठबंधन अद्यतन-दर-संदर्भ , setkey
और अन्य स्थानीय data.table जैसे कार्यfrollapply
- एक अन्य बिंदु आधार आर या tidyverse का उपयोग करने से संबंधित है। मैं data.table + tidyverse (dplyr / readr / tidyr को छोड़कर) दोनों का उपयोग करता हूं। बड़े कार्यों के लिए, मैं अक्सर बेंचमार्क करता हूं, उदाहरण के लिए,
stringr::str_*
परिवार बनाम आधार आर फ़ंक्शन और मुझे लगता है कि आधार आर एक निश्चित सीमा तक तेज हैं और उन का उपयोग करते हैं। बिंदु है, अपने आप को केवल tidyverse या data.table या ... के लिए न रखें, काम पूरा करने के लिए अन्य विकल्पों का पता लगाएं।
इन पहलुओं में से कई ऊपर वर्णित बिंदुओं के साथ अंतर-संबंधित हैं
संचालन की जटिलता
अद्यतन-दर-संदर्भ
आप पता लगा सकते हैं कि क्या {dtplyr} इन ऑपरेशनों का समर्थन करते हैं, खासकर जब वे संयुक्त होते हैं।
इंटरैक्टिव सत्र के दौरान, छोटे या बड़े डेटासेट के साथ काम करते समय एक और उपयोगी ट्रिक, {data.table} वास्तव में प्रोग्रामिंग को कम करने और समय की जबरदस्त गणना करने के अपने वादे पर खरा उतरता है।
गति और 'सुपरचार्ज्ड रोनेम्स' दोनों के लिए दोहराए जाने वाले चर के लिए कुंजी सेट करना (चर नाम निर्दिष्ट किए बिना सबसेट)।
dt <- data.table(iris)
setkey(dt, Species)
dt['setosa', do_something(...), ...]
dt['virginica', do_another(...), ...]
dt['setosa', more(...), ...]
# `by` argument can also be omitted, particularly useful during interactive session
# this ultimately becomes what I call 'naked' syntax, just type what you want to do, without any placeholders.
# It's simply elegant
dt['setosa', do_something(...), Species, ...]
यदि आपके कार्यों में पहले उदाहरण की तरह केवल सरल शामिल हैं, तो {dtplyr} कार्य प्राप्त कर सकते हैं। जटिल / असमर्थित लोगों के लिए, आप इस गाइड का उपयोग {dtplyr} के अनुवादित लोगों की तुलना करने के लिए कर सकते हैं कि कैसे अनुभवी data.table उपयोगकर्ता डेटा के साथ तेज और मेमोरी कुशल तरीके से कोड करेंगे। अनुवाद का मतलब यह नहीं है कि बड़े डेटा के विभिन्न मामलों से निपटने के लिए अलग-अलग तकनीकें हो सकती हैं। इससे भी अधिक बड़े डेटासेट के लिए, आप {data.able} को {disk.frame} , {fst} और {drake} और अन्य भयानक पैकेजों के साथ मिला सकते हैं ताकि यह सबसे अच्छा हो सके। एक {big.data.table} भी है, लेकिन यह वर्तमान में निष्क्रिय है।
मुझे उम्मीद है कि यह सभी की मदद करेगा। आपका दिन शुभ हो ☺☺
dplyr
कि आप में अच्छी तरह से नहीं कर सकतेdata.table
? यदि नहीं, तो स्विचिंग इससेdata.table
बेहतर होने वाला हैdtplyr
।