Comment lire des données lorsque certains nombres contiennent des virgules comme séparateur de milliers?

117

J'ai un fichier csv où certaines des valeurs numériques sont exprimées sous forme de chaînes avec des virgules comme séparateur de milliers, par exemple "1,513"au lieu de 1513. Quelle est la manière la plus simple de lire les données dans R?

Je peux utiliser read.csv(..., colClasses="character"), mais je dois ensuite supprimer les virgules des éléments pertinents avant de convertir ces colonnes en colonnes numériques, et je ne trouve pas de moyen efficace de le faire.

Rob Hyndman
la source

Réponses:

142

Je ne sais pas comment l' read.csvinterpréter correctement, mais vous pouvez utiliser gsubpour remplacer ","par "", puis convertir la chaîne en numericutilisant as.numeric:

y <- c("1,200","20,000","100","12,111")
as.numeric(gsub(",", "", y))
# [1]  1200 20000 100 12111

Cela a également été répondu précédemment sur R-Help (et au deuxième trimestre ici ).

Vous pouvez également prétraiter le fichier, par exemple avec sedsous unix.

Shane
la source
60

Vous pouvez demander à read.table ou read.csv d'effectuer cette conversion pour vous de manière semi-automatique. Commencez par créer une nouvelle définition de classe, puis créez une fonction de conversion et définissez-la comme méthode "as" en utilisant la fonction setAs comme ceci:

setClass("num.with.commas")
setAs("character", "num.with.commas", 
        function(from) as.numeric(gsub(",", "", from) ) )

Ensuite, exécutez read.csv comme:

DF <- read.csv('your.file.here', 
   colClasses=c('num.with.commas','factor','character','numeric','num.with.commas'))
Greg Snow
la source
3
C'est une très belle astuce. Il pourrait être utilisé pour la conversion à l'importation (par exemple, la conversion des valeurs Y / N en vecteur logique à l'aide de setAs("character", "logical.Y.N", function(from) c(Y=TRUE,N=FALSE)[from] )).
Marek
1
La même astuce utilise dans un problème similaire . Et d'ajouter: on pourrait utiliser soit setClass("num.with.commas")ou suppresMessage(setAs(.....))pour éviter le message sur la classe manquante.
Marek
Salut Greg, merci de partager cette fonction pratique. Lors de l'exécution, je reçois l'avertissement suivant: dans la méthode pour 'contraindre' avec signature '"caractère", "num.with.commas"': pas de définition pour la classe "num.with.commas" Toute idée du problème ici, J'ai votre mot de code pour mot?
TheGoat
J'ai vérifié le lien de problème similaire et j'ai vu que je devais définir la classe! Merci pour l'astuce.
TheGoat
17

Je souhaite utiliser R plutôt que prétraiter les données, car cela facilite la révision des données. Suite à la suggestion d'utilisation de Shane gsub, je pense que c'est à peu près aussi net que je peux le faire:

x <- read.csv("file.csv",header=TRUE,colClasses="character")
col2cvt <- 15:41
x[,col2cvt] <- lapply(x[,col2cvt],function(x){as.numeric(gsub(",", "", x))})
Rob Hyndman
la source
Est-ce que colClasses = "char" ne force pas toutes les colonnes à être char, auquel cas les autres à part 15:41 sont également char? Peut-être que le fait de laisser read.csv () décider, puis de convertir ceux qui dans les cols 15:41 peut vous donner plus de colonnes numériques.
Dirk Eddelbuettel
Oui, mais comme ma question l'a noté, toutes les autres colonnes sont des caractères. Je pourrais utiliser as.is = TRUE à la place, ce qui serait plus général. Mais laisser read.csv () décider en utilisant les arguments par défaut n'est pas utile car cela convertira tout ce qui ressemble à un caractère en un facteur qui causera des problèmes pour les colonnes numériques car elles ne se convertissent pas correctement en utilisant as.numeric () .
Rob Hyndman
Vous devriez envisager de définir l'argument dec = dans la table de lecture sur ".". C'est la valeur par défaut pour read.csv2 mais la virgule est câblée dans read.csv ().
IRTFM
15

Cette question date de plusieurs années, mais je suis tombée dessus, ce qui signifie que d'autres le feront peut-être.

La readrbibliothèque / package a quelques fonctionnalités intéressantes. L'une d'elles est une manière agréable d'interpréter des colonnes «désordonnées», comme celles-ci.

library(readr)
read_csv("numbers\n800\n\"1,800\"\n\"3500\"\n6.5",
          col_types = list(col_numeric())
        )

Cela donne

Source: trame de données locale [4 x 1]

  numbers
    (dbl)
1   800.0
2  1800.0
3  3500.0
4     6.5

Un point important lors de la lecture de fichiers: soit vous devez pré-traiter, comme le commentaire ci-dessus concernant sed, soit vous devez traiter pendant la lecture . Souvent, si vous essayez de réparer les choses après coup, certaines suppositions dangereuses sont difficiles à trouver. (C'est pourquoi les fichiers plats sont si mauvais en premier lieu.)

Par exemple, si je n'avais pas signalé le col_types, j'aurais obtenu ceci:

> read_csv("numbers\n800\n\"1,800\"\n\"3500\"\n6.5")
Source: local data frame [4 x 1]

  numbers
    (chr)
1     800
2   1,800
3    3500
4     6.5

(Notez que c'est maintenant un chr( character) au lieu d'un numeric.)

Ou, plus dangereusement, s'il était assez long et que la plupart des premiers éléments ne contenaient pas de virgules:

> set.seed(1)
> tmp <- as.character(sample(c(1:10), 100, replace=TRUE))
> tmp <- c(tmp, "1,003")
> tmp <- paste(tmp, collapse="\"\n\"")

(tel que les derniers éléments ressemblent à :)

\"5\"\n\"9\"\n\"7\"\n\"1,003"

Ensuite, vous aurez du mal à lire cette virgule!

> tail(read_csv(tmp))
Source: local data frame [6 x 1]

     3"
  (dbl)
1 8.000
2 5.000
3 5.000
4 9.000
5 7.000
6 1.003
Warning message:
1 problems parsing literal data. See problems(...) for more details. 
Mike Williamson
la source
7

une dplyrsolution utilisant mutate_allet des tuyaux

dites que vous avez ce qui suit:

> dft
Source: local data frame [11 x 5]

   Bureau.Name Account.Code   X2014   X2015   X2016
1       Senate          110 158,000 211,000 186,000
2       Senate          115       0       0       0
3       Senate          123  15,000  71,000  21,000
4       Senate          126   6,000  14,000   8,000
5       Senate          127 110,000 234,000 134,000
6       Senate          128 120,000 159,000 134,000
7       Senate          129       0       0       0
8       Senate          130 368,000 465,000 441,000
9       Senate          132       0       0       0
10      Senate          140       0       0       0
11      Senate          140       0       0       0

et souhaitez supprimer les virgules des variables année X2014-X2016 et les convertir en numérique. aussi, disons que X2014-X2016 sont lus comme des facteurs (par défaut)

dft %>%
    mutate_all(funs(as.character(.)), X2014:X2016) %>%
    mutate_all(funs(gsub(",", "", .)), X2014:X2016) %>%
    mutate_all(funs(as.numeric(.)), X2014:X2016)

mutate_allapplique la (les) fonction (s) à l'intérieur funsaux colonnes spécifiées

Je l'ai fait séquentiellement, une fonction à la fois (si vous utilisez plusieurs fonctions à l'intérieur, funsvous créez des colonnes supplémentaires et inutiles)

Paul
la source
3
mutate_eachest obsolète. Voulez-vous mettre à jour votre réponse avec mutate_atou similaire?
T_T
6

"Prétraitement" dans R:

lines <- "www, rrr, 1,234, ttt \n rrr,zzz, 1,234,567,987, rrr"

Peut utiliser readLinessur un textConnection. Supprimez ensuite uniquement les virgules qui se trouvent entre les chiffres:

gsub("([0-9]+)\\,([0-9])", "\\1\\2", lines)

## [1] "www, rrr, 1234, ttt \n rrr,zzz, 1234567987, rrr"

Il est également utile de savoir, mais pas directement pertinent pour cette question, que les virgules en tant que séparateurs décimaux peuvent être gérées par read.csv2 (automatiquement) ou read.table (avec le réglage du paramètre 'dec').

Edit: Plus tard, j'ai découvert comment utiliser colClasses en concevant une nouvelle classe. Voir:

Comment charger df avec séparateur 1000 dans R en tant que classe numérique?

IRTFM
la source
Merci, c'était un bon pointeur mais cela ne fonctionne pas pour les chiffres contenant plusieurs décimales, par exemple 1 234 567,89 - nécessaire pour contourner ce problème pour importer une feuille de calcul Google dans R, voir stackoverflow.com/a/30020171/3096626 pour un simple fonction qui fait le travail pour plusieurs marques décimales
flexponsive
4

Si le nombre est séparé par "." et décimales par "," (1.200.000,00) en appelant, gsubvous devezset fixed=TRUE as.numeric(gsub(".","",y,fixed=TRUE))

aca
la source
3

Un moyen très pratique est readr::read_delim-famille. Prenant l'exemple d'ici: L'importation de csv avec plusieurs séparateurs dans R vous pouvez le faire comme suit:

txt <- 'OBJECTID,District_N,ZONE_CODE,COUNT,AREA,SUM
1,Bagamoyo,1,"136,227","8,514,187,500.000000000000000","352,678.813105723350000"
2,Bariadi,2,"88,350","5,521,875,000.000000000000000","526,307.288878142830000"
3,Chunya,3,"483,059","30,191,187,500.000000000000000","352,444.699742995200000"'

require(readr)
read_csv(txt) # = read_delim(txt, delim = ",")

Ce qui aboutit au résultat attendu:

# A tibble: 3 × 6
  OBJECTID District_N ZONE_CODE  COUNT        AREA      SUM
     <int>      <chr>     <int>  <dbl>       <dbl>    <dbl>
1        1   Bagamoyo         1 136227  8514187500 352678.8
2        2    Bariadi         2  88350  5521875000 526307.3
3        3     Chunya         3 483059 30191187500 352444.7
Rentrop
la source
3

En utilisant la fonction read_delim, qui fait partie de la bibliothèque readr , vous pouvez spécifier un paramètre supplémentaire:

locale = locale(decimal_mark = ",")

read_delim("filetoread.csv", ';", locale = locale(decimal_mark = ","))

* Un point-virgule sur la deuxième ligne signifie que read_delim lira les valeurs séparées par des points-virgules csv.

Cela aidera à lire tous les nombres avec une virgule comme des nombres appropriés.

Cordialement

Mateusz Kania

Mateusz Kania
la source
3

Nous pouvons également utiliser readr::parse_number, les colonnes doivent être des caractères cependant. Si nous voulons l'appliquer pour plusieurs colonnes, nous pouvons parcourir les colonnes en utilisantlapply

df[2:3] <- lapply(df[2:3], readr::parse_number)
df

#  a        b        c
#1 a    12234       12
#2 b      123  1234123
#3 c     1234     1234
#4 d 13456234    15342
#5 e    12312 12334512

Ou utilisez mutate_atfrom dplyrpour l'appliquer à des variables spécifiques.

library(dplyr)
df %>% mutate_at(2:3, readr::parse_number)
#Or
df %>% mutate_at(vars(b:c), readr::parse_number)

Les données

df <- data.frame(a = letters[1:5], 
                 b = c("12,234", "123", "1,234", "13,456,234", "123,12"),
                 c = c("12", "1,234,123","1234", "15,342", "123,345,12"), 
                 stringsAsFactors = FALSE)
Ronak Shah
la source
0

Je pense que le prétraitement est la voie à suivre. Vous pouvez utiliser Notepad ++ qui a une option de remplacement d'expression régulière.

Par exemple, si votre fichier ressemblait à ceci:

"1,234","123","1,234"
"234","123","1,234"
123,456,789

Ensuite, vous pouvez utiliser l'expression régulière "([0-9]+),([0-9]+)"et la remplacer par\1\2

1234,"123",1234
"234","123",1234
123,456,789

Ensuite, vous pouvez utiliser x <- read.csv(file="x.csv",header=FALSE)pour lire le fichier.

Jacob
la source
23
Tout ce que vous pouvez écrire, vous devriez le faire. Le faire à la main présente une possibilité d'erreur, en plus d'être peu reproductible.
hadley