分组后特定列去重性能问题:原因排查与优化方案
Hey Stefan, great question—let's figure out why your code is dragging on that 500k-row dataset and get it running faster!
Why Your Current Code Is Slow
Your code works perfectly for small test sets, but with 500k rows, a few key bottlenecks are causing the slowdown:
- Dual grouped
mutateoperations: Eachmutateaftergroup_by(year, id)forces dplyr to iterate over every group twice, adding unnecessary processing overhead. - Inefficient duplicate checking: When grouped by
yearandid, every row in a group shares the same ID. Usingduplicated(id)here is a roundabout way to identify the first row in each group, and it's less efficient than purpose-built grouped row counters. - Slower conditional logic: Base R's
ifelseis flexible but lacks the performance optimizations of dplyr'sif_elseor data.table'sfifelse.
Optimized Solutions
Option 1: Streamlined dplyr Code
We can condense your logic into a single mutate call using dplyr's optimized row_number() function, which is designed for fast grouped operations. We'll also use if_else for faster conditional checks:
library(dplyr) Test %>% group_by(year, id) %>% mutate(oppervlakt = if_else(row_number() == 1, oppervlakt, 0)) %>% ungroup() # Always ungroup to avoid unexpected behavior in later operations
This removes the intermediate is_duplicate column and cuts down on redundant group processing, which should give you a noticeable speed boost on large datasets.
Option 2: Blazing Fast with data.table
For datasets with 100k+ rows, data.table is almost always faster than dplyr because it's built for in-place modifications and highly optimized grouped operations. Here's how to rewrite your logic:
library(data.table) # Convert your data frame to a data table (in-place, no extra memory copy) setDT(Test) # Update the `oppervlakt` column directly by (year, id) groups Test[, oppervlakt := fifelse(rowid(year, id) == 1, oppervlakt, 0)]
rowid(year, id) generates a sequential counter for each (year, id) group, and fifelse is data.table's fast, type-safe alternative to ifelse. This operates directly on your data without creating copies, making it memory-efficient and lightning-fast for large datasets.
Bonus Performance Tips
- Optimize data types: Ensure
yearis stored as an integer (not a factor or character) andiduses a compact type (integer or short character strings). Factors add unnecessary overhead to grouping operations. - Update dplyr: If you stick with dplyr, make sure you're using the latest version—recent releases include major performance improvements for grouped operations.
- Trim extra columns: If your
Testdataset has columns you don't need for this operation, subset them first withselect()to reduce memory usage and speed up processing.
内容的提问来源于stack exchange,提问作者Stefan Musch

