data.table مقابل dplyr: يمكن للمرء أن يفعل شيئا جيدا الآخر لا يمكن أو لا بشكل ضعيف؟




(2)

نظرة عامة

أنا معتاد نسبيا مع data.table ، ليس كثيرا مع dplyr . لقد قرأت بعض المقالات القصيرة dplyr والأمثلة التي برزت على SO ، وحتى الآن استنتاجاتي هي:

  1. data.table و dplyr قابلة للمقارنة في السرعة ، باستثناء عندما يكون هناك العديد من المجموعات (أي> 10-100K) ، وفي بعض الحالات الأخرى (انظر المقاييس أدناه)
  2. dplyr لديه بناء الجملة أكثر سهولة
  3. خلاصات dplyr (أو will) تفاعلات DB المحتملة
  4. هناك بعض الاختلافات في الوظائف الثانوية (راجع "الأمثلة / الاستخدام" أدناه)

في رأيي 2. لا تتحمل الكثير من الوزن لأنني على دراية بها data.table ، على الرغم من أنني أفهم أنه بالنسبة للمستخدمين الجدد على حد سواء سيكون عاملا كبيرا. أود أن أتجنب الجدل حول أيهما أكثر بديهية ، حيث أن ذلك غير ذي صلة data.table المحدد من منظور شخص يعرف بالفعل data.table . وأود أيضا تجنب مناقشة حول كيفية "أكثر بديهية" يؤدي إلى تحليل أسرع (صحيح بالتأكيد ، ولكن مرة أخرى ، وليس ما أنا مهتم أكثر هنا).

سؤال

ما أريد معرفته هو:

  1. هل هناك مهام تحليلية أسهل بكثير من التعليمة البرمجية مع حزمة واحدة أو غيرها من الأشخاص المألوفين للحزم (أي مجموعة من ضغطات المفاتيح المطلوبة مقابل المستوى المطلوب من الباطنية ، حيث يكون أقل من كل شيء جيد).
  2. هل توجد مهام تحليلية يتم إجراؤها بشكل كبير (أي أكثر من 2x) بشكل أكثر كفاءة في حزمة واحدة مقابل حزمة أخرى.

أحد الأسئلة الأخيرة التي قدمتها منظمة الـ SO جعلتني أفكر في هذا الأمر أكثر من ذلك بقليل ، لأنه حتى هذه النقطة لم أكن أعتقد أن dplyr ستقدم الكثير مما يمكن أن أفعله بالفعل في data.table . هنا هو الحل dplyr (البيانات في نهاية Q):

dat %.%
  group_by(name, job) %.%
  filter(job != "Boss" | year == min(year)) %.%
  mutate(cumu_job2 = cumsum(job2))

الذي كان أفضل بكثير من محاولة الاختراق في حل data.table . ومع ذلك ، data.table حلول data.table الجيدة هي أيضًا جيدة (بفضل Jean-Robert و Arun ، ولاحظ هنا أنني أفضّل عبارة واحدة على الحل الأمثل تمامًا):

setDT(dat)[,
  .SD[job != "Boss" | year == min(year)][, cumjob := cumsum(job2)], 
  by=list(id, job)
]

قد تبدو البنية اللغوية للأخيرة غاية في الباطنية ، ولكنها في الواقع بسيطة إلى حد ما إذا كنت معتادًا على data.table (أي لا تستخدم بعض الحيل الباطنية).

من الناحية المثالية ما أود رؤيته هو أن بعض الأمثلة الجيدة كانت طريقة data.table أو data.table أكثر إيجازًا بشكل جوهري أو تؤدي بشكل أفضل.

أمثلة

استعمال
  • لا يسمح dplyr بالعمليات المجمعة التي تقوم بإرجاع عدد عشوائي من الصفوف (من سؤال eddi ، ملاحظة: هذا يبدو أنه سيتم تنفيذه في dplyr 0.5 ، أيضًا ،beginneR يظهر إمكانية العمل حول استخدام do في الإجابة على سؤال @ eddi) .
  • data.table يدعم الانضمام المتداول (شكرا @ dholstius) وكذلك الانضمام التداخل
  • data.table داخليًا تعبيرات النموذج DT[col == value] أو DT[col %in% values] للسرعة من خلال الفهرسة التلقائية التي تستخدم البحث الثنائي أثناء استخدام بنية R R القاعدة نفسها. انظر هنا لمزيد من التفاصيل ومعايير دقيقة.
  • يقدم dplyr إصدارات تقييم قياسية للوظائف (على سبيل المثال ، regroup ، summarize_each_ ) التي يمكن أن تبسط الاستخدام البرمجي لـ dplyr (ملاحظة الاستخدام البرمجي data.table من الممكن بالتأكيد ، يتطلب فقط بعض التفكير الدقيق ، الاستبدال / الاقتباس ، إلخ ، على الأقل إلى حد علمي )
المعايير

البيانات

هذا هو المثال الأول الذي عرضته في قسم الأسئلة.

dat <- structure(list(id = c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 
2L, 2L, 2L, 2L, 2L, 2L), name = c("Jane", "Jane", "Jane", "Jane", 
"Jane", "Jane", "Jane", "Jane", "Bob", "Bob", "Bob", "Bob", "Bob", 
"Bob", "Bob", "Bob"), year = c(1980L, 1981L, 1982L, 1983L, 1984L, 
1985L, 1986L, 1987L, 1985L, 1986L, 1987L, 1988L, 1989L, 1990L, 
1991L, 1992L), job = c("Manager", "Manager", "Manager", "Manager", 
"Manager", "Manager", "Boss", "Boss", "Manager", "Manager", "Manager", 
"Boss", "Boss", "Boss", "Boss", "Boss"), job2 = c(1L, 1L, 1L, 
1L, 1L, 1L, 0L, 0L, 1L, 1L, 1L, 0L, 0L, 0L, 0L, 0L)), .Names = c("id", 
"name", "year", "job", "job2"), class = "data.frame", row.names = c(NA, 
-16L))

في الرد المباشر على عنوان السؤال ...

dplyr بالتأكيد يفعل أشياء لا يمكن أن data.table .

نقطتك # 3

خلاصات dplyr (أو will) تفاعلات DB المحتملة

هو إجابة مباشرة على سؤالك ولكنك غير مرتفع إلى مستوى عالٍ بما فيه الكفاية. dplyr هو حقا واجهة أمامية قابلة للتمديد لآليات تخزين البيانات المتعددة حيث تكون data.table امتدادا لواحد.

انظر إلى dplyr الخلفية ، مع جميع الأهداف باستخدام نفس القواعد ، حيث يمكنك توسيع الأهداف والمعالجين في الإرادة. data.table is, from the dplyr perspective, one of those targets.

You will never (I hope) see a day that data.table attempts to translate your queries to create SQL statements that operate with on-disk or networked data stores.

dplyr can possibly do things data.table will not or might not do as well.

Based on the design of working in-memory, data.table could have a much more difficult time extending itself into parallel processing of queries than dplyr .

In response to the in-body questions...

استعمال

Are there analytical tasks that are a lot easier to code with one or the other package for people familiar with the packages (ie some combination of keystrokes required vs. required level of esotericism, where less of each is a good thing).

This may seem like a punt but the real answer is no. People familiar with tools seem to use the either the one most familiar to them or the one that is actually the right one for the job at hand. With that being said, sometimes you want to present a particular readability, sometimes a level of performance, and when you have need for a high enough level of both you may just need another tool to go along with what you already have to make clearer abstractions.

Performance

Are there analytical tasks that are performed substantially (ie more than 2x) more efficiently in one package vs. another.

Again, no. data.table excels at being efficient in everything it does where dplyr gets the burden of being limited in some respects to the underlying data store and registered handlers.

This means when you run into a performance issue with data.table you can be pretty sure it is in your query function and if it is actually a bottleneck with data.table then you've won yourself the joy of filing a report. This is also true when dplyr is using data.table as the back-end; you may see some overhead from dplyr but odds are it is your query.

When dplyr has performance issues with back-ends you can get around them by registering a function for hybrid evaluation or (in the case of databases) manipulating the generated query prior to execution.

Also see the accepted answer to when is plyr better than data.table?


نحتاج إلى تغطية هذه الجوانب على الأقل لتوفير إجابة / مقارنة شاملة (بدون ترتيب معين من حيث الأهمية): Speed Memory usage Syntax Features .

هدفي هو تغطية كل واحد منها بشكل واضح قدر الإمكان من منظور data.table.

ملاحظة: ما لم يذكر خلاف ذلك صراحة ، بالإشارة إلى dplyr ، نشير إلى واجهة data.frame الخاصة بـ dplyr والتي تكون internals في C ++ باستخدام Rcpp.

بناء جملة data.table متناسقة في شكلها - DT[i, j, by] . للحفاظ على i و j ومن خلال التصميم. من خلال الحفاظ على العمليات المرتبطة ببعضها البعض ، يسمح بسهولة تحسين العمليات للسرعة والأهم من استخدام الذاكرة ، وأيضا توفير بعض الميزات القوية ، مع الحفاظ على التناسق في بناء الجملة.

1. السرعة

تم إضافة عدد قليل من المعايير (على الرغم من معظمها على عمليات التجميع) إلى السؤال الذي يظهر بالفعل data.table أسرع من dplyr مثل عدد المجموعات و / أو الصفوف لتجميع حسب الزيادة ، بما في ذلك المعايير من قبل مات في تجميع من 10 مليون إلى 2 مليار صف (100 جيجابايت في ذاكرة الوصول العشوائي) على 100 - 10 مليون مجموعة وأعمدة تجميع متفاوتة ، والتي تقارن أيضا pandas .

بالنسبة للمعايير ، سيكون من الرائع تغطية هذه الجوانب المتبقية أيضًا:

  • عمليات التجميع التي تتضمن مجموعة فرعية من الصفوف - أي DT[x > val, sum(y), by = z] .

  • حدد العمليات الأخرى مثل التحديث والانضمام .

  • أيضا قياس بصمة الذاكرة لكل عملية بالإضافة إلى وقت التشغيل.

2. استخدام الذاكرة

  1. العمليات التي تنطوي على filter() أو slice() في dplyr يمكن أن تكون غير فعالة في الذاكرة (على كل من data.frames و data.tables). انظر هذا المنصب .

    لاحظ أن تعليق هادلي يتحدث عن السرعة (أن dplyr سريع وفير بالنسبة له) ، في حين أن القلق الرئيسي هنا هو الذاكرة .

  2. تسمح واجهة data.table في اللحظة الواحدة بتعديل / تحديث الأعمدة حسب المرجع (لاحظ أننا لا نحتاج إلى إعادة تعيين النتيجة مرة أخرى إلى متغير).

    # sub-assign by reference, updates 'y' in-place
    DT[x >= 1L, y := NA]
    

    ولكن لن يتم تحديث dplyr أبدًا بالرجوع إليه. سيكون المعادل dplyr (لاحظ أن النتيجة تحتاج إلى إعادة تعيين):

    # copies the entire 'y' column
    ans <- DF %>% mutate(y = replace(y, which(x >= 1L), NA))
    

    مصدر القلق لهذا هو الشفافية المرجعية . تحديث كائن data.table حسب المرجع ، خاصة داخل وظيفة قد لا يكون مرغوبًا دائمًا. ولكن هذه ميزة مفيدة بشكل لا يصدق: يمكنك الاطلاع على this المنشور this المشاركات في الحالات المثيرة للاهتمام. ونحن نريد الاحتفاظ بها.

    لذلك نحن نعمل على تصدير الدالة shallow() في data.table التي ستزود المستخدم بكل من الاحتمالات . على سبيل المثال ، إذا كان من المستحسن عدم تعديل البيانات المدخلة في أحد الوظائف ، فيمكن عندئذٍ القيام بما يلي:

    foo <- function(DT) {
        DT = shallow(DT)          ## shallow copy DT
        DT[, newcol := 1L]        ## does not affect the original DT 
        DT[x > 2L, newcol := 2L]  ## no need to copy (internally), as this column exists only in shallow copied DT
        DT[x > 2L, x := 3L]       ## have to copy (like base R / dplyr does always); otherwise original DT will 
                                  ## also get modified.
    }
    

    بعدم استخدام shallow() ، يتم الاحتفاظ بالوظائف القديمة:

    bar <- function(DT) {
        DT[, newcol := 1L]        ## old behaviour, original DT gets updated by reference
        DT[x > 2L, x := 3L]       ## old behaviour, update column x in original DT.
    }
    

    من خلال إنشاء نسخة ضحلة باستخدام shallow() ، ندرك أنك لا تريد تعديل الكائن الأصلي. نحن نحرص على كل شيء داخليًا لضمان أنه في الوقت الذي تضمن فيه أيضًا نسخ الأعمدة التي تعدلها فقط عند الضرورة القصوى . عند التنفيذ ، يجب أن يقوم هذا بتسوية مشكلة الشفافية المرجعية تمامًا أثناء توفير المستخدم بكلتا الإمكانتين.

    أيضا ، مرة واحدة shallow() يتم تصدير واجهة data.table dplyr يجب تجنب تقريبا جميع النسخ. حتى أولئك الذين يفضلون بناء الجملة dplyr يمكن استخدامها مع data.tables.

    لكنه سيظل يفتقر إلى العديد من الميزات التي توفرها data.table ، بما في ذلك (sub) -assignment بالرجوع إليها.

  3. التجميع أثناء الانضمام:

    افترض أن لديك اثنين من data.tables كما يلي:

    DT1 = data.table(x=c(1,1,1,1,2,2,2,2), y=c("a", "a", "b", "b"), z=1:8, key=c("x", "y"))
    #    x y z
    # 1: 1 a 1
    # 2: 1 a 2
    # 3: 1 b 3
    # 4: 1 b 4
    # 5: 2 a 5
    # 6: 2 a 6
    # 7: 2 b 7
    # 8: 2 b 8
    DT2 = data.table(x=1:2, y=c("a", "b"), mul=4:3, key=c("x", "y"))
    #    x y mul
    # 1: 1 a   4
    # 2: 2 b   3
    

    وتود الحصول على sum(z) * mul لكل صف في DT2 أثناء الانضمام بواسطة الأعمدة x,y . يمكننا إما:

    • 1) تجميع DT1 للحصول على sum(z) ، 2) أداء صلة و 3) مضاعفة (أو)

      # data.table way
      DT1[, .(z = sum(z)), keyby = .(x,y)][DT2][, z := z*mul][]
      
      # dplyr equivalent
      DF1 %>% group_by(x, y) %>% summarise(z = sum(z)) %>% 
          right_join(DF2) %>% mutate(z = z * mul)
      
    • 2) القيام بكل ذلك دفعة واحدة (باستخدام ميزة by = .EACHI ):

      DT1[DT2, list(z=sum(z) * mul), by = .EACHI]
      

    ما هي الفائدة؟

    • ليس لدينا تخصيص الذاكرة للنتيجة المتوسطة.

    • ليس لدينا مجموعة / تجزئة مرتين (واحد للتجميع وغيرها للتسجيل).

    • والأهم من ذلك ، أن العملية التي أردنا تنفيذها واضحة من خلال النظر في j في (2).

    تحقق من هذه الوظيفة للحصول على شرح مفصل عن by = .EACHI . لا تتحقق أي نتائج وسيطة ، ويتم تنفيذ الانضمام + تجميع دفعة واحدة.

    إلقاء نظرة على this ، this this الوظائف لسيناريوهات الاستخدام الحقيقي.

    في dplyr سيكون عليك الانضمام أو التجميع أو التجميع أولاً ومن ثم الانضمام ، لا يكون أي منهما فعالاً ، من حيث الذاكرة (والتي بدورها تترجم إلى السرعة).

  4. التحديث والانضمام:

    النظر في كود data.table المبين أدناه:

    DT1[DT2, col := i.mul]
    

    يضيف / يقوم بتحديث عمود عمود DT1 مع mul من DT2 على تلك الصفوف حيث يتطابق عمود المفتاح DT2 مع DT1 . لا أعتقد أن هناك ما يعادل بالضبط هذه العملية في dplyr ، أي دون تجنب عملية *_join ، والتي يجب أن تنسخ DT1 بأكمله فقط لإضافة عمود جديد إليها ، وهو أمر غير ضروري.

    تحقق من هذه المشاركة للحصول على سيناريو استخدام حقيقي.

لتلخيص ، من المهم أن ندرك أن كل شيء من أهمية التحسين. كما قال جريس هوبر ، مانع النانوسيكند الخاص بك !

3. بناء الجملة

دعونا ننظر الآن في بناء الجملة . وعلق هادلي here :

جداول البيانات سريعة للغاية ، لكني أعتقد أن إحكامها يجعل من الصعب تعلمها ، ومن الصعب قراءة التعليمات البرمجية التي تستخدمها بعد كتابتها ...

أجد هذه الملاحظة لا معنى لها لأنها ذاتية للغاية. ما يمكننا محاولة ربما هو مقارنة التناسق في بناء الجملة . سنقوم مقارنة data.table وبناء الجملة dplyr جنبا إلى جنب.

سنعمل مع البيانات الوهمية الموضحة أدناه:

DT = data.table(x=1:10, y=11:20, z=rep(1:2, each=5))
DF = as.data.frame(DT)
  1. عمليات التجميع / التحديث الأساسية.

    # case (a)
    DT[, sum(y), by = z]                       ## data.table syntax
    DF %>% group_by(z) %>% summarise(sum(y)) ## dplyr syntax
    DT[, y := cumsum(y), by = z]
    ans <- DF %>% group_by(z) %>% mutate(y = cumsum(y))
    
    # case (b)
    DT[x > 2, sum(y), by = z]
    DF %>% filter(x>2) %>% group_by(z) %>% summarise(sum(y))
    DT[x > 2, y := cumsum(y), by = z]
    ans <- DF %>% group_by(z) %>% mutate(y = replace(y, which(x > 2), cumsum(y)))
    
    # case (c)
    DT[, if(any(x > 5L)) y[1L]-y[2L] else y[2L], by = z]
    DF %>% group_by(z) %>% summarise(if (any(x > 5L)) y[1L] - y[2L] else y[2L])
    DT[, if(any(x > 5L)) y[1L] - y[2L], by = z]
    DF %>% group_by(z) %>% filter(any(x > 5L)) %>% summarise(y[1L] - y[2L])
    
    • بناء جملة data.table هو مطول و dplyr تماما مطول. الأمور أكثر أو أقل في حالة (أ).

    • في الحالة (ب) ، كان علينا استخدام filter() في dplyr في حين تلخيص . لكن أثناء التحديث ، كان علينا تحريك المنطق داخل mutate() . ومع ذلك ، في data.table ، نعبر عن كلتا العمليات بنفس المنطق - تعمل على الصفوف حيث x > 2 ، ولكن في الحالة الأولى ، تحصل على sum(y) ، بينما في الحالة الثانية تحديث تلك الصفوف لـ y مع مجموعها التراكمي.

      هذا هو ما نعنيه عندما نقول أن نموذج DT[i, j, by] ثابت .

    • وبالمثل في حالة (c) ، عندما يكون لدينا شرط if-else ، فإننا قادرون على التعبير عن المنطق "كما هو" في كل من data.table و dplyr. ومع ذلك ، إذا كنا نرغب في إرجاع هذه الصفوف فقط if شرط ما يرضي ويتخطي خلاف ذلك ، لا يمكننا استخدام summarise() مباشرة (AFAICT). يتعين علينا filter() أولاً ثم تلخيصها لأن summarise() دائمًا ما يتوقع قيمة واحدة .

      بينما تقوم بإرجاع نفس النتيجة ، فإن استخدام filter() هنا يجعل العملية الفعلية أقل وضوحًا.

      قد يكون من الممكن جدا استخدام filter() في الحالة الأولى أيضا (لا يبدو واضحا لي) ، لكن وجهة نظري هي أنه لا ينبغي لنا ذلك.

  2. التجميع / التحديث على أعمدة متعددة

    # case (a)
    DT[, lapply(.SD, sum), by = z]                     ## data.table syntax
    DF %>% group_by(z) %>% summarise_each(funs(sum)) ## dplyr syntax
    DT[, (cols) := lapply(.SD, sum), by = z]
    ans <- DF %>% group_by(z) %>% mutate_each(funs(sum))
    
    # case (b)
    DT[, c(lapply(.SD, sum), lapply(.SD, mean)), by = z]
    DF %>% group_by(z) %>% summarise_each(funs(sum, mean))
    
    # case (c)
    DT[, c(.N, lapply(.SD, sum)), by = z]     
    DF %>% group_by(z) %>% summarise_each(funs(n(), mean))
    
    • في الحالة (a) ، تكون الشفرات متكافئة تقريباً. يستخدم lapply() وظيفة قاعدة مألوفة ، في حين يقدم *_each() جنبا إلى جنب مع حفنة من الوظائف إلى funs() .

    • data.table's := يتطلب تقديم أسماء الأعمدة ، في حين تقوم dplyr بتوليدها تلقائيًا.

    • في حالة (b) ، تكون بنية dplyr بسيطة نسبياً. تحسين التجميعات / التحديثات على وظائف متعددة على قائمة data.table.

    • في حالة (c) ، سيعود dplyr n() أكبر عدد من الأعمدة ، بدلاً من مرة واحدة. في data.table ، كل ما يتعين علينا القيام به هو إعادة القائمة في j . سيصبح كل عنصر في القائمة عمودًا في النتيجة. لذا ، يمكننا استخدام ، مرة أخرى ، الدالة الأساسية المألوفة c() لسَلسَلة .N إلى list تُرجع list .

    ملاحظة: مرة أخرى ، في data.table ، كل ما يتعين علينا القيام به هو إعادة القائمة في j . سيصبح كل عنصر في القائمة عمودًا في النتيجة. يمكنك استخدام c() ، as.list() ، lapply() ، list() إلخ ... الوظائف الأساسية لإنجاز ذلك ، دون الحاجة إلى تعلم أي وظائف جديدة.

    سوف تحتاج إلى تعلم المتغيرات الخاصة فقط - .N و .N على الأقل. المكافئ في dplyr هي n() و .

  3. ينضم

    يوفر dplyr وظائف منفصلة لكل نوع من أنواع الاتصال حيث يسمح data.table بالارتباطات باستخدام نفس بناء الجملة DT[i, j, by] (وبسبب). كما يوفر merge.data.table() مكافئة كبديل.

    setkey(DT1, x, y)
    
    # 1. normal join
    DT1[DT2]            ## data.table syntax
    left_join(DT2, DT1) ## dplyr syntax
    
    # 2. select columns while join    
    DT1[DT2, .(z, i.mul)]
    left_join(select(DT2, x, y, mul), select(DT1, x, y, z))
    
    # 3. aggregate while join
    DT1[DT2, .(sum(z) * i.mul), by = .EACHI]
    DF1 %>% group_by(x, y) %>% summarise(z = sum(z)) %>% 
        inner_join(DF2) %>% mutate(z = z*mul) %>% select(-mul)
    
    # 4. update while join
    DT1[DT2, z := cumsum(z) * i.mul, by = .EACHI]
    ??
    
    # 5. rolling join
    DT1[DT2, roll = -Inf]
    ??
    
    # 6. other arguments to control output
    DT1[DT2, mult = "first"]
    ??
    
    • قد يجد البعض وظيفة منفصلة لكل صلات أجمل بكثير (يسارًا ، يمينًا ، داخليًا ، مضادًا ، شبه ، إلخ) ، بينما قد يعجب البعض الآخر DT[i, j, by] أو merge() التي تشبه القاعدة R.

    • ومع ذلك dbertr ينضم تفعل ذلك تماما. لا شيء آخر. لا شيء اقل.

    • data.tables يمكن تحديد الأعمدة أثناء الانضمام (2) ، وفي dplyr سوف تحتاج إلى select() أولا على كل data.frames قبل الانضمام كما هو موضح أعلاه. وإلا فإنك ستقوم بإنشاء ارتباط مع الأعمدة غير الضرورية فقط لإزالتها لاحقًا وهذا غير فعال.

    • يمكن تجميع data.tables أثناء ضمه (3) وتحديثه أيضًا أثناء الانضمام (4) ، باستخدام ميزة by = .EACHI . لماذا materialse نتيجة الانضمام بالكامل لإضافة / تحديث عدد قليل من الأعمدة؟

    • data.table قادرة على الالتفافات الانضمام (5) - لفة إلى الأمام ، LOCF ، لفة إلى الوراء ، NOCB ، nearest .

    • data.table لديها أيضا mult = الحجة التي تحدد أولا ، آخر أو كل المباريات (6).

    • data.table has allow.cartesian = TRUE وسيطة للحماية من الصلات غير الصالحة العرضية.

مرة أخرى ، يكون بناء الجملة متناسقاً مع DT[i, j, by] مع وسيطات إضافية تسمح بالتحكم في المخرجات أكثر.

  1. do() ...

    تم تصميم تلخيص dplyr خصيصًا للوظائف التي تعرض قيمة واحدة. إذا كانت الدالة تقوم بإرجاع قيم متعددة / غير متساوية ، فسيتعين عليك اللجوء إلى do() . عليك أن تعرف مسبقا عن جميع وظائفك قيمة الإرجاع.

    DT[, list(x[1], y[1]), by = z]                 ## data.table syntax
    DF %>% group_by(z) %>% summarise(x[1], y[1]) ## dplyr syntax
    DT[, list(x[1:2], y[1]), by = z]
    DF %>% group_by(z) %>% do(data.frame(.$x[1:2], .$y[1]))
    
    DT[, quantile(x, 0.25), by = z]
    DF %>% group_by(z) %>% summarise(quantile(x, 0.25))
    DT[, quantile(x, c(0.25, 0.75)), by = z]
    DF %>% group_by(z) %>% do(data.frame(quantile(.$x, c(0.25, 0.75))))
    
    DT[, as.list(summary(x)), by = z]
    DF %>% group_by(z) %>% do(data.frame(as.list(summary(.$x))))
    
    • مكافئ .SD هو .

    • في data.table ، يمكنك رمي أي شيء في j - الشيء الوحيد الذي يجب تذكره هو إعادة قائمة بحيث يتم تحويل كل عنصر في القائمة إلى عمود.

    • في dplyr ، لا يمكن القيام بذلك. يجب أن تلجأ إلى do() اعتمادًا على مدى تأكدك من ما إذا كانت وظيفتك ستعيد دائمًا قيمة واحدة. وانها بطيئة جدا.

مرة أخرى ، بناء جملة data.table متناسقة مع DT[i, j, by] . يمكننا فقط الاستمرار في إلقاء تعبيرات في j دون الحاجة إلى القلق بشأن هذه الأشياء.

إلقاء نظرة على هذا السؤال SO وهذا واحد . أتساءل عما إذا كان من الممكن التعبير عن الجواب بشكل مباشر باستخدام بنية dplyr ...

لتلخيص ، أبرزت بشكل خاص عدة حالات حيث تكون بنية dplyr إما غير فعالة أو محدودة أو لا تجعل العمليات مباشرة. هذا بشكل خاص لأن data.table يحصل على قدر كبير من رد الفعل عن بناء الجملة "أصعب على القراءة / التعلّم" (مثل اللصق / الرابط أعلاه). معظم المشاركات التي تغطي dplyr تتحدث عن معظم العمليات المباشرة. وهذا رائع. ولكن من المهم أن نحقق أيضًا قيوده على البُنية والميزات ، وأنا لا أزال أرغب في نشرها.

وقد data.table لها المراوغات كذلك (بعض التي أشرت إلى أننا نحاول إصلاح). نحن نحاول أيضًا تحسين انضمام data.table كما أشرت here .

ولكن ينبغي للمرء أيضا النظر في عدد من الميزات التي تفتقر dplyr بالمقارنة مع data.table.

4. الميزات

لقد أشرت إلى معظم الميزات هنا وأيضا في هذا المنصب. بالإضافة الى:

  • fread - لقد أصبح قارئ الملفات السريع متاحًا منذ فترة طويلة الآن.

  • fwrite - جديد في التطوير الحالي ، v1.9.7 ، كاتب ملفات سريع متوازي متوفر الآن. انظر هذا المنصب للحصول على شرح مفصل حول التنفيذ و #1664 لتتبع التطورات الأخرى.

  • الفهرسة التلقائية - ميزة أخرى مفيدة لتحسين بناء جملة R القاعدة كما هو داخليًا.

  • التجميع المخصص : تقوم dplyr تلقائيًا بفرز النتائج عن طريق تجميع المتغيرات أثناء summarise() ، والذي قد لا يكون مرغوبًا دائمًا.

  • العديد من المزايا في ينضم data.table (من أجل سرعة وكفاءة الذاكرة وبناء الجملة) المذكورة أعلاه.

  • الإضافات غير equi: هي ميزة جديدة متوفرة من الإصدار v1.9.7 +. يسمح بالارتباطات باستخدام عوامل التشغيل الأخرى <=, <, >, >= مع كافة المزايا الأخرى للارتباطات data.table.

  • تم تنفيذ روابط النطاق المتداخلة في data.table مؤخرًا. تحقق من هذه المشاركة للحصول على نظرة عامة مع المعايير.

  • وظيفة setorder() في data.table تسمح بإعادة ترتيب البيانات بسرعة.

  • يوفر dplyr واجهة لقواعد البيانات باستخدام نفس البنية ، والتي data.table لا في الوقت الحالي.

  • يوفر data.table مكافئات أسرع من مجموعة العمليات من v1.9.7 + (كتبه Jan fsetdiff ) - fsetdiff ، fintersect ، funion و fsetequal مع all الحجة إضافية (كما هو الحال في SQL).

  • يتم تحميل البيانات data.table مع عدم وجود تحذيرات حجب ولديه آلية موصوفة here أجل [.data.frame التوافق عند تمريرها إلى أي حزمة R. يغير dplyr filter وظائف القاعدة ، lag و [ مما قد يسبب مشاكل ؛ على سبيل المثال here here .

أخيرا:

  • على قواعد البيانات - لا يوجد أي سبب لعدم تمكن Data.table من توفير واجهة مشابهة ، ولكن هذه ليست أولوية الآن. قد تحصل على ارتطام إذا كان المستخدمون يحبون كثيرا هذه الميزة .. غير متأكد.

  • على التوازي - كل شيء صعب ، حتى يمضي أحدهم ويفعل ذلك. بالطبع سوف يستغرق الجهد (خيط آمن).

    • يتم الآن تحقيق تقدم (في v1.9.7 devel) نحو موازاة الأجزاء المستهلكة للوقت المعروفة لتحقيق مكاسب أداء متزايدة باستخدام OpenMP .




dplyr