2017-08-09 2 views
4

Ich möchte sequential citation numbers für eine Zahl in R generieren. Die Zahlen sollten durch einen Bindestrich getrennt werden, wenn sie sequentiell sind. Ansonsten sind die Zahlen durch ein Komma getrennt. Zum Beispiel sollten die Nummern 1, 2, 3, 5, 6, 8, 9, 10, 11 and 13 als 1-3,5,6,8-11,13 ausgegeben werden.Sequentielle Zitation Nummerierung in R: separate Zahlen durch Bindestrich, wenn sequentiell - Komma hinzufügen, wenn nicht

Diese Frage wurde previously answered for c#, und ich habe eine Funktion geschrieben, die für R funktioniert, aber diese Funktion kann verbessert werden. Ich poste diese Frage als eine Referenz für andere, die ein ähnliches Bedürfnis haben könnten. Wenn Sie eine ähnliche Frage für R finden (was ich nicht getan habe), stimmen Sie bitte ab, um zu schließen, und ich werde die Frage entfernen.

Die folgende Funktion ist nicht sehr elegant, scheint aber die Aufgabe zu erfüllen. Wie kann man die Funktion kürzer und eleganter gestalten?

x <- c(1,2,3,5,6,8,9,10,11,13) 

library(zoo) ## the function requires zoo::na.approx function 

##' @title Generate hyphenated sequential citation from an integer vector 
##' @param x integer vector giving citation or page numbers 
##' @importFrom zoo na.approx 

seq.citation <- function(x) { 

## Result if lenght of the integer vector is 1. 
if(length(x) == 1) return(x) else { 

## Sort 
x <- sort(x) 

## Difference 
df <- diff(x) 

## Index to determine start and end points 
ind <- c("start", rep("no", length(df)-1), "end") 
ind[which(df > 1)] <- "end" 

## Temporary start point vector 
sts <- which(ind == "end") + 1 
ind[sts[sts < length(ind)]] <- "start" 

## Replace the first index element 
ind[1] <- "start" 

## Replace the last index element, if preceding one is "end" 
if(ind[length(ind)-1] == "end") ind[length(ind)] <- "start" 

## Groups for comma separation using "start" as the determining value. 
grp <- rep(NA, length(x)) 
grp[which(ind == "start")] <- 1:length(grp[which(ind == "start")]) 
grp <- zoo::na.approx(grp, method = "constant", rule = 2) 

## Split sequences by group 
seqs <- split(x, grp) 

seqs <- lapply(seqs, function(k) { 
    if(length(k) == 1) k else { 
    if(length(k) == 2) paste(k[1], k[2], sep = ",") else { 
    paste(k[1], k[length(k)], sep = "-") 
    }} 
}) 

## Result 
return(do.call("paste", c(seqs, sep = ","))) 
} 
} 

seq.citation(x) 
# [1] "1-3,5,6,8-11,13" 
+2

See, auch ein ähnliches [post] (https://stackoverflow.com/questions/34636461/function-to-summarize -vector-of-numbers-as-a-string) –

Antwort

6

Sie können dies über die Basis R leicht tun tapply, mit

paste(tapply(x, cumsum(c(1, diff(x) != 1)), function(i) 
    ifelse(length(i) > 2, paste0(head(i, 1), '-', tail(i, 1)), 
          paste(i, collapse = ','))), collapse = ',') 

[1] "1-3,5,6,8-11,13" 
+1

Aktualisiert. Schau mal – Sotos

+1

Erfüllt die Anforderungen von "elegant" und "kurz" und funktioniert für meinen aktuellen Datensatz. Vielen Dank! Man kann noch 'as.character()' zum Ergebnis hinzufügen, so dass auch Elemente mit der Länge 1 als Zeichen zurückgegeben werden. – Mikko

4

Das für Ihr Beispiel funktioniert und sollte recht allgemein sein.

# get run lengths of differences, with max value of 2 
r <- rle(c(1, pmin(diff(x), 2))) 

# paste selected x values with appropriate separator 
res <- paste0(x[c(1, cumsum(r$lengths))], c("-", ",")[r$values], collapse="") 

# drop final character, which is a separator 
res <- substr(res, 1, nchar(res)-1) 

Das gibt

res 
[1] "1-3,5-6,8-11,13" 
+0

@sotos füge ein 'gsub (" - \\ d + - "," - ", res)' hinzu und du solltest fast fertig sein. – lmo

+0

Aus irgendeinem Grund funktioniert meine Regex mit 'gsub' nicht mit 8-10-11. Ich hatte nicht genug Kaffee, um herauszufinden, warum. – lmo

+0

Schön! Diese Lösung gibt 1-1 für den Anfang von 'res' zurück, wenn die Länge des ersten Elements 1 ist. Das sollte irgendwie behoben werden. Auch bekomme ich ein Komma am Ende von jedem 'res' – Mikko

1

Es ist, natürlich, die seqToHumanReadable Funktion aus dem "R.utils" -Paket.

library(R.utils) 
seqToHumanReadable(x) 
# [1] "1-3, 5, 6, 8-11, 13" 
seqToHumanReadable(x, tau = 1) ## If you want 5-6 and not 5, 6 
# [1] "1-3, 5-6, 8-11, 13" 

Das Aussehen des Ergebnisses auch gesteuert werden kann:

seqToHumanReadable(x, delimiter = "...", collapse = " | ") 
# [1] "1...3 | 5 | 6 | 8...11 | 13" 
Verwandte Themen