-
Notifications
You must be signed in to change notification settings - Fork 1k
Topn doing order |> head/tail #5167
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
b972e13
a8f4096
1767174
7c9ce8b
43cc962
1687cb6
fc7b948
90a652f
c513efb
ab25d3f
6785cf5
78502c7
c9b7a07
87aa734
446ca7f
db055f5
2b16d6a
13caa28
0fba260
ff541fd
fe0e6f9
e1d037e
d8cf910
5e547df
134d0a3
37efbf3
538d2f6
c70fbc1
f8ff588
2dfb740
0604714
6a162de
e9688fb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| \name{topn} | ||
| \alias{topn} | ||
| \alias{which.max} | ||
| \alias{which.min} | ||
| \title{ Top n values index } | ||
| \description{ | ||
| \code{topn} returns the indices of the smallest (resp. largest) \code{n} values of x. This is an extension \code{\link{which.min}} (\code{\link{which.max}}) which only return the index of the minimum (resp. maximum). | ||
|
|
||
| Especially, for large vectors this method will be faster and less memory intensive than \code{order} since no full sort is performed. | ||
|
|
||
| \code{bit64::integer64} type is also supported. | ||
| } | ||
|
|
||
| \usage{ | ||
| topn(x, n, na.last=TRUE, decreasing=FALSE, sorted=FALSE) | ||
| } | ||
| \arguments{ | ||
| \item{x}{ A numeric, complex, character or logical vector. } | ||
| \item{n}{ A numeric vector length 1. How many indices to select. } | ||
| \item{na.last}{ Control treatment of \code{NA}s. If \code{TRUE}, missing values in the data are put last; if \code{FALSE}, they are put first. } | ||
| \item{decreasing}{ Logical. Default is \code{FALSE}. Indicating whether the order should be increasing or decreasing. } | ||
| \item{sorted}{ Logical. Default is \code{FALSE}. Indicating whether order should be sorted with respect to decreasing. } | ||
| } | ||
|
|
||
| \value{ | ||
| An integer vector giving the indicies of the \code{n} smallest (largest) for \code{decreasing=FALSE (TRUE)} elements of \code{x}. | ||
| } | ||
|
|
||
| \examples{ | ||
| x = c(1:4, 0:5, 11) | ||
| # indices of smallest 3 values | ||
| topn(x, 3) | ||
| # indices of largest 3 values | ||
| topn(x, 3, decreasing = TRUE) | ||
|
|
||
| ## NA's can be put to front or back | ||
| x = c(NA, 1:4) | ||
| topn(x, 5) | ||
| topn(x, 5, na.last=FALSE) | ||
|
|
||
| } | ||
| \seealso{ | ||
| \code{\link{data.table}}, \code{\link{order}}, \code{\link{which.max}}, \code{\link{which.min}} | ||
| } | ||
| \keyword{ data } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,199 @@ | ||||||||||||||||||||||||||
| #include "data.table.h" | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| static inline void swap(int *a, int *b) { int tmp=*a; *a=*b; *b=tmp; } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| static inline bool icmp(const int *x, int i, int j, bool min, bool nalast) { | ||||||||||||||||||||||||||
| if (x[i]==x[j]) return i > j; | ||||||||||||||||||||||||||
| if (x[i]==NA_INTEGER) return nalast; | ||||||||||||||||||||||||||
| if (x[j]==NA_INTEGER) return !nalast; | ||||||||||||||||||||||||||
| return min ? x[i] < x[j] : x[i] > x[j]; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| static inline bool dcmp(const double *x, int i, int j, bool min, bool nalast) { | ||||||||||||||||||||||||||
| if (x[i]==x[j] || (isnan(x[i]) && isnan(x[j]))) return i > j; | ||||||||||||||||||||||||||
| if (isnan(x[i])) return nalast; | ||||||||||||||||||||||||||
| if (isnan(x[j])) return !nalast; | ||||||||||||||||||||||||||
| return min ? x[i] < x[j] : x[i] > x[j]; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| static inline bool i64cmp(const int64_t *x, int i, int j, bool min, bool nalast) { | ||||||||||||||||||||||||||
| if (x[i]==x[j]) return i > j; | ||||||||||||||||||||||||||
| if (x[i]==NA_INTEGER64) return nalast; | ||||||||||||||||||||||||||
| if (x[j]==NA_INTEGER64) return !nalast; | ||||||||||||||||||||||||||
| return min ? x[i] < x[j] : x[i] > x[j]; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| static inline bool scmp(const SEXP *restrict x, int i, int j, bool min, bool nalast) { | ||||||||||||||||||||||||||
| if (strcmp(CHAR(x[i]), CHAR(x[j])) == 0) return i > j; | ||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since Might it help to call |
||||||||||||||||||||||||||
| if (x[i]==NA_STRING) return nalast; | ||||||||||||||||||||||||||
| if (x[j]==NA_STRING) return !nalast; | ||||||||||||||||||||||||||
| return min ? strcmp(CHAR(x[i]),CHAR(x[j]))<0 : strcmp(CHAR(x[i]),CHAR(x[j]))>0; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| static inline bool ccmp(const Rcomplex *x, int i, int j, bool min, bool nalast) { | ||||||||||||||||||||||||||
| if (ISNAN_COMPLEX(x[i]) && ISNAN_COMPLEX(x[j])) return i > j; | ||||||||||||||||||||||||||
| if (x[i].r==x[j].r) { | ||||||||||||||||||||||||||
| if (x[i].i==x[j].i) return i > j; | ||||||||||||||||||||||||||
| return min ? x[i].i < x[j].i : x[i].i > x[j].i; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| if (ISNAN_COMPLEX(x[i])) return nalast; | ||||||||||||||||||||||||||
| if (ISNAN_COMPLEX(x[j])) return !nalast; | ||||||||||||||||||||||||||
| return min ? x[i].r < x[j].r : x[i].r > x[j].r; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // compare value of node with values of left/right child nodes and sift value down if value of child node is smaller than parent (for minheap) | ||||||||||||||||||||||||||
| #undef SIFT | ||||||||||||||||||||||||||
| #define SIFT(CMP) { \ | ||||||||||||||||||||||||||
| int smallest, l, r; \ | ||||||||||||||||||||||||||
| while(true) { \ | ||||||||||||||||||||||||||
| smallest = k; \ | ||||||||||||||||||||||||||
| l = (k << 1) + 1; \ | ||||||||||||||||||||||||||
| r = l+1; \ | ||||||||||||||||||||||||||
| if (l < len && CMP(VAL,INDEX[l],INDEX[smallest],min,nalast)) \ | ||||||||||||||||||||||||||
| smallest = l; \ | ||||||||||||||||||||||||||
| if (r < len && CMP(VAL,INDEX[r],INDEX[smallest],min,nalast)) \ | ||||||||||||||||||||||||||
| smallest = r; \ | ||||||||||||||||||||||||||
| if (smallest != k) { \ | ||||||||||||||||||||||||||
| swap(&INDEX[k], &INDEX[smallest]); \ | ||||||||||||||||||||||||||
| k = smallest; \ | ||||||||||||||||||||||||||
| } else { \ | ||||||||||||||||||||||||||
| break; \ | ||||||||||||||||||||||||||
| } \ | ||||||||||||||||||||||||||
| } \ | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| // for finding decreasing topn build minheap and add values if they exceed | ||||||||||||||||||||||||||
| // minimum by overwriting minimum and following down sifting | ||||||||||||||||||||||||||
| #undef HEAPN | ||||||||||||||||||||||||||
| #define HEAPN(CTYPE, RTYPE, CMP, SORTED) { \ | ||||||||||||||||||||||||||
| const CTYPE *restrict VAL = (const CTYPE *)RTYPE(x); \ | ||||||||||||||||||||||||||
| for (int i=n/2; i>=0; --i) { k=i; len=n; SIFT(CMP); } \ | ||||||||||||||||||||||||||
| for (int i=n; i<xlen; ++i) { \ | ||||||||||||||||||||||||||
| if (CMP(VAL,INDEX[0],i,min,nalast)) { \ | ||||||||||||||||||||||||||
| INDEX[0] = i; \ | ||||||||||||||||||||||||||
| k=0; len=n; SIFT(CMP); \ | ||||||||||||||||||||||||||
| } \ | ||||||||||||||||||||||||||
| } \ | ||||||||||||||||||||||||||
| if (SORTED) { \ | ||||||||||||||||||||||||||
| for (int i=0; i<n; ++i) { \ | ||||||||||||||||||||||||||
| swap(&INDEX[0], &INDEX[n-1-i]); \ | ||||||||||||||||||||||||||
| k=0; len=n-1-i; SIFT(CMP); \ | ||||||||||||||||||||||||||
| ians[n-1-i] = INDEX[n-1-i]+1; \ | ||||||||||||||||||||||||||
| } \ | ||||||||||||||||||||||||||
| } else { \ | ||||||||||||||||||||||||||
| for (int i=0; i<n; ++i) { \ | ||||||||||||||||||||||||||
| ians[i] = INDEX[i]+1; \ | ||||||||||||||||||||||||||
| } \ | ||||||||||||||||||||||||||
| } \ | ||||||||||||||||||||||||||
| free(INDEX); \ | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| SEXP topn(SEXP x, SEXP nArg, SEXP naArg, SEXP ascArg, SEXP sortedArg) { | ||||||||||||||||||||||||||
| if (!isInteger(nArg) || LENGTH(nArg)!=1 || INTEGER(nArg)[0]<=0 || INTEGER(nArg)[0]==NA_INTEGER) error(_("topn(x,n) only implemented for n > 0.")); | ||||||||||||||||||||||||||
| if (!IS_TRUE_OR_FALSE(ascArg)) error(_("%s must be TRUE or FALSE"), "decreasing"); | ||||||||||||||||||||||||||
| if (!IS_TRUE_OR_FALSE(naArg)) error(_("%s must be TRUE or FALSE"), "na.last"); | ||||||||||||||||||||||||||
| if (!IS_TRUE_OR_FALSE(sortedArg)) error(_("%s must be TRUE or FALSE"), "sorted"); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const int xlen = LENGTH(x); | ||||||||||||||||||||||||||
| int n = INTEGER(nArg)[0]; | ||||||||||||||||||||||||||
| if (n > xlen) { | ||||||||||||||||||||||||||
| warning(_("n should be smaller or equal than length(x) but provided n=%d and length(x)=%d.\n Coercing n to length(x)."), n, xlen); | ||||||||||||||||||||||||||
| n = xlen; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const bool min = LOGICAL(ascArg)[0]; | ||||||||||||||||||||||||||
| const bool nalast = LOGICAL(naArg)[0]; | ||||||||||||||||||||||||||
| const bool sorted = LOGICAL(sortedArg)[0]; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| SEXP ans; | ||||||||||||||||||||||||||
| int k, len; | ||||||||||||||||||||||||||
| ans = PROTECT(allocVector(INTSXP, n)); | ||||||||||||||||||||||||||
| int *restrict ians = INTEGER(ans); | ||||||||||||||||||||||||||
| int *restrict INDEX = malloc(n*sizeof(int)); | ||||||||||||||||||||||||||
ben-schwen marked this conversation as resolved.
Show resolved
Hide resolved
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not |
||||||||||||||||||||||||||
| if (!INDEX) error(_("Internal error: Couldn't allocate memory for heap indices.")); // # nocov | ||||||||||||||||||||||||||
| for (int i=0; i<n; ++i) INDEX[i] = i; | ||||||||||||||||||||||||||
| switch(TYPEOF(x)) { | ||||||||||||||||||||||||||
| case LGLSXP: case INTSXP: { HEAPN(int, INTEGER, icmp, sorted); } break; | ||||||||||||||||||||||||||
| case REALSXP: { | ||||||||||||||||||||||||||
| if (INHERITS(x, char_integer64)) { HEAPN(int64_t, REAL, i64cmp, sorted); } | ||||||||||||||||||||||||||
| else { HEAPN(double, REAL, dcmp, sorted); } break; } | ||||||||||||||||||||||||||
| case CPLXSXP: { HEAPN(Rcomplex, COMPLEX, ccmp, sorted); } break; | ||||||||||||||||||||||||||
| case STRSXP: { HEAPN(SEXP, STRING_PTR, scmp, sorted); } break; | ||||||||||||||||||||||||||
|
Comment on lines
+116
to
+121
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||
| default: | ||||||||||||||||||||||||||
| free(INDEX); error(_("Type '%s' not supported by topn."), type2char(TYPEOF(x))); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| UNPROTECT(1); | ||||||||||||||||||||||||||
| return(ans); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| #undef QUICKN | ||||||||||||||||||||||||||
| #define QUICKN(CTYPE, RTYPE, CMP, SWAP) \ | ||||||||||||||||||||||||||
| CTYPE *ix = (CTYPE *)RTYPE(x); \ | ||||||||||||||||||||||||||
| CTYPE *ians = (CTYPE *)RTYPE(ans); \ | ||||||||||||||||||||||||||
| unsigned long l = 0, ir = xlen - 1; \ | ||||||||||||||||||||||||||
| for (;;) { \ | ||||||||||||||||||||||||||
| if (ir <= l + 1) { \ | ||||||||||||||||||||||||||
| if (ir == l + 1 && CMP(ix,l,ir,min,nalast)) { \ | ||||||||||||||||||||||||||
| SWAP(ix+l, ix+ir); \ | ||||||||||||||||||||||||||
| } \ | ||||||||||||||||||||||||||
| break; \ | ||||||||||||||||||||||||||
| } else { \ | ||||||||||||||||||||||||||
| unsigned long mid = (l + ir) >> 1; \ | ||||||||||||||||||||||||||
| SWAP(ix+mid, ix+l + 1); \ | ||||||||||||||||||||||||||
| if (CMP(ix,l,ir,min,nalast)) { \ | ||||||||||||||||||||||||||
| SWAP(ix+l, ix+ir); \ | ||||||||||||||||||||||||||
| } \ | ||||||||||||||||||||||||||
| if (CMP(ix,l+1,ir,min,nalast)) { \ | ||||||||||||||||||||||||||
| SWAP(ix+l+1, ix+ir); \ | ||||||||||||||||||||||||||
| } \ | ||||||||||||||||||||||||||
| if (CMP(ix,l,l+1,min,nalast)) { \ | ||||||||||||||||||||||||||
| SWAP(ix+l, ix+l+1); \ | ||||||||||||||||||||||||||
| } \ | ||||||||||||||||||||||||||
| unsigned long i = l + 1, j = ir; \ | ||||||||||||||||||||||||||
| for (;;) { \ | ||||||||||||||||||||||||||
| do i++; while (CMP(ix,l+1,i,min,nalast)); \ | ||||||||||||||||||||||||||
| do j--; while (CMP(ix,j,l+1,min,nalast)); \ | ||||||||||||||||||||||||||
| if (j < i) break; \ | ||||||||||||||||||||||||||
| SWAP(ix+i, ix+j); \ | ||||||||||||||||||||||||||
| } \ | ||||||||||||||||||||||||||
| SWAP(ix+l+1, ix+j); \ | ||||||||||||||||||||||||||
| if (j >= n) ir = j - 1; \ | ||||||||||||||||||||||||||
| if (j <= n) l = i; \ | ||||||||||||||||||||||||||
| } \ | ||||||||||||||||||||||||||
| } \ | ||||||||||||||||||||||||||
| memcpy(ians, ix, n * sizeof(CTYPE)) | ||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is somewhat scary for character vectors, but I'm not seeing anything that would break right now. From the GC generations viewpoint, From the reference counts viewpoint, it'll be one less than what it should be for elements of |
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| static inline void iswap(int *a, int *b) {int tmp=*a; *a=*b; *b=tmp;} | ||||||||||||||||||||||||||
| static inline void dswap(double *a, double *b) {double tmp=*a; *a=*b; *b=tmp;} | ||||||||||||||||||||||||||
| static inline void i64swap(int64_t *a, int64_t *b) {int64_t tmp=*a; *a=*b; *b=tmp;} | ||||||||||||||||||||||||||
| static inline void cswap(Rcomplex *a, Rcomplex *b) {Rcomplex tmp=*a; *a=*b; *b=tmp;} | ||||||||||||||||||||||||||
| static inline void sswap(SEXP *a, SEXP *b) {SEXP tmp=*a; *a=*b; *b=tmp;} | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| SEXP quickn(SEXP x, SEXP nArg, SEXP naArg, SEXP ascArg) { | ||||||||||||||||||||||||||
| if (!isInteger(nArg) || LENGTH(nArg)!=1 || INTEGER(nArg)[0]<=0 || INTEGER(nArg)[0]==NA_INTEGER) error(_("topn(x,n) only implemented for n > 0.")); | ||||||||||||||||||||||||||
| if (!IS_TRUE_OR_FALSE(ascArg)) error(_("%s must be TRUE or FALSE"), "decreasing"); | ||||||||||||||||||||||||||
| if (!IS_TRUE_OR_FALSE(naArg)) error(_("%s must be TRUE or FALSE"), "na.last"); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const int xlen = LENGTH(x); | ||||||||||||||||||||||||||
| int n = INTEGER(nArg)[0]; | ||||||||||||||||||||||||||
| x = PROTECT(duplicate(x)); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| const bool min = LOGICAL(ascArg)[0]; | ||||||||||||||||||||||||||
| const bool nalast = LOGICAL(naArg)[0]; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| SEXP ans; | ||||||||||||||||||||||||||
| ans = PROTECT(allocVector(TYPEOF(x), n)); | ||||||||||||||||||||||||||
| switch(TYPEOF(x)) { | ||||||||||||||||||||||||||
| case LGLSXP: case INTSXP: { QUICKN(int, INTEGER, icmp, iswap); } break; | ||||||||||||||||||||||||||
| case REALSXP: { | ||||||||||||||||||||||||||
| if (INHERITS(x, char_integer64)) { QUICKN(int64_t, REAL, i64cmp, i64swap); } | ||||||||||||||||||||||||||
| else { QUICKN(double, REAL, dcmp, dswap); } break; } | ||||||||||||||||||||||||||
| case CPLXSXP: { QUICKN(Rcomplex, COMPLEX, ccmp, cswap); } break; | ||||||||||||||||||||||||||
| case STRSXP: { QUICKN(SEXP, STRING_PTR, scmp, sswap); } break; | ||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Could also be Lines 116 to 117 in 2654599
|
||||||||||||||||||||||||||
| default: | ||||||||||||||||||||||||||
| error(_("Type '%s' not supported by quickn."), type2char(TYPEOF(x))); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| copyMostAttrib(x, ans); | ||||||||||||||||||||||||||
| UNPROTECT(2); | ||||||||||||||||||||||||||
| return(ans); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do think this will be most useful when equipped to sort multiple vectors, so my sense is the signature should be
topn(n, x, ...)ortopn(n, ...).Here's code that looks
topn()-able:https://github.com/search?q=lang%3AR+%2Forder%5C%28.*%5C%29%5C%5B1%3A%5B0-9%5D%2F&type=code
That said, I could only really find one usage that needs multiple vectors (could be I have a bad regex)!
https://github.com/search?q=lang%3AR+%2F%5B%5Ea-z0-9._%5Dorder%5C%28%5B%5E%28%29%3D%5D*%2C%5B%5E%3D%5Cn%5C%5B%5C%5D%29%5D*%5C%29%5C%5B1%3A%5B0-9%5D%2F&type=code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But n will be most often scalar, no? Then it is not clear why it would be first argument. Then it is also not pipeable for data input.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea of adding
...support, however, being pipeable seems more advantageous nowadays.