 <p>Here's a simple option, using <code>data.table</code> instead:</p> <pre><code>library(data.table) dt = as.data.table(your_df) setkey(dt, id, date) # in versions 1.9.3+ dt[CJ(unique(id), unique(date)), .N, by = .EACHI] # id date N # 1: Andrew13 2006-08-03 0 # 2: Andrew13 2007-09-11 1 # 3: Andrew13 2008-06-12 0 # 4: Andrew13 2008-10-11 0 # 5: Andrew13 2009-07-03 0 # 6: John12 2006-08-03 1 # 7: John12 2007-09-11 0 # 8: John12 2008-06-12 0 # 9: John12 2008-10-11 0 #10: John12 2009-07-03 0 #11: Lisa825 2006-08-03 0 #12: Lisa825 2007-09-11 0 #13: Lisa825 2008-06-12 0 #14: Lisa825 2008-10-11 0 #15: Lisa825 2009-07-03 1 #16: Tom2993 2006-08-03 0 #17: Tom2993 2007-09-11 0 #18: Tom2993 2008-06-12 1 #19: Tom2993 2008-10-11 1 #20: Tom2993 2009-07-03 0 </code></pre> <p>In versions 1.9.2 or before the equivalent expression omits the explicit <code>by</code>:</p> <pre><code>dt[CJ(unique(id), unique(date)), .N] </code></pre> <p>The idea is to create all possible pairs of <code>id</code> and <code>date</code> (which is what the <code>CJ</code> part does), and then merge it back, counting occurrences.</p>