Découpage d'un énorme fichier csv (3,5 Go) à lire dans R

87

J'ai donc un fichier de données (séparé par des points-virgules) qui contient beaucoup de détails et des lignes incomplètes (amenant Access et SQL à s'étouffer). Il s'agit d'un ensemble de données au niveau du comté divisé en segments, sous-segments et sous-sous-segments (pour un total d'environ 200 facteurs) pendant 40 ans. Bref, c'est énorme, et ça ne rentrera pas dans la mémoire si j'essaie simplement de le lire.

Donc ma question est la suivante, étant donné que je veux tous les comtés, mais seulement une seule année (et juste le plus haut niveau de segment ... conduisant à environ 100000 lignes à la fin), quelle serait la meilleure façon de procéder pour obtenir ce rollup en R?

Actuellement, j'essaie de couper les années non pertinentes avec Python, de contourner la limite de taille du fichier en lisant et en opérant sur une ligne à la fois, mais je préférerais une solution R-only (packages CRAN OK). Existe-t-il une manière similaire de lire dans les fichiers un morceau à la fois dans R?

Toutes les idées seraient grandement appréciées.

Mise à jour:

  • Contraintes
    • Doit utiliser ma machine, donc pas d'instance EC2
    • Aussi R-seulement que possible. La vitesse et les ressources ne sont pas concernées dans ce cas ... à condition que ma machine n'explose pas ...
    • Comme vous pouvez le voir ci-dessous, les données contiennent des types mixtes, sur lesquels je devrai opérer plus tard
  • Les données
    • Les données sont de 3,5 Go, avec environ 8,5 millions de lignes et 17 colonnes
    • Quelques milliers de lignes (~ 2k) sont mal formées, avec une seule colonne au lieu de 17
      • Ceux-ci sont totalement sans importance et peuvent être supprimés
    • Je n'ai besoin que d'environ 100000 lignes de ce fichier (voir ci-dessous)

Exemple de données:

County; State; Year; Quarter; Segment; Sub-Segment; Sub-Sub-Segment; GDP; ...
Ada County;NC;2009;4;FIRE;Financial;Banks;80.1; ...
Ada County;NC;2010;1;FIRE;Financial;Banks;82.5; ...
NC  [Malformed row]
[8.5 Mill rows]

Je veux découper certaines colonnes et choisir deux des 40 années disponibles (2009-2010 de 1980-2020), afin que les données puissent tenir dans R:

County; State; Year; Quarter; Segment; GDP; ...
Ada County;NC;2009;4;FIRE;80.1; ...
Ada County;NC;2010;1;FIRE;82.5; ...
[~200,000 rows]

Résultats:

Après avoir bricolé toutes les suggestions faites, j'ai décidé que readLines, suggéré par JD et Marek, fonctionnerait le mieux. J'ai donné le chèque à Marek parce qu'il a donné un exemple d'implémentation.

J'ai reproduit une version légèrement adaptée de l'implémentation de Marek pour ma réponse finale ici, en utilisant strsplit et cat pour ne garder que les colonnes que je veux.

Il convient également de noter que c'est BEAUCOUP moins efficace que Python ... comme dans, Python parcourt le fichier de 3,5 Go en 5 minutes tandis que R en prend environ 60 ... mais si tout ce que vous avez est R, c'est le ticket.

## Open a connection separately to hold the cursor position
file.in <- file('bad_data.txt', 'rt')
file.out <- file('chopped_data.txt', 'wt')
line <- readLines(file.in, n=1)
line.split <- strsplit(line, ';')
# Stitching together only the columns we want
cat(line.split[[1]][1:5], line.split[[1]][8], sep = ';', file = file.out, fill = TRUE)
## Use a loop to read in the rest of the lines
line <- readLines(file.in, n=1)
while (length(line)) {
  line.split <- strsplit(line, ';')
  if (length(line.split[[1]]) > 1) {
    if (line.split[[1]][3] == '2009') {
        cat(line.split[[1]][1:5], line.split[[1]][8], sep = ';', file = file.out, fill = TRUE)
    }
  }
  line<- readLines(file.in, n=1)
}
close(file.in)
close(file.out)

Échecs par approche:

  • sqldf
    • C'est certainement ce que j'utiliserai pour ce type de problème à l'avenir si les données sont bien formées. Cependant, si ce n'est pas le cas, SQLite s'étouffe.
  • MapReduce
    • Pour être honnête, les documents m'ont un peu intimidé sur celui-ci, donc je n'ai pas essayé. Il semblait qu'il fallait que l'objet soit également en mémoire, ce qui irait à l'encontre du point si tel était le cas.
  • grande mémoire
    • Cette approche est clairement liée aux données, mais elle ne peut gérer qu'un seul type à la fois. En conséquence, tous mes vecteurs de caractères ont chuté lorsqu'ils sont placés dans une grande table. Si j'ai besoin de concevoir de grands ensembles de données pour l'avenir, j'envisagerais d'utiliser uniquement des nombres juste pour garder cette option en vie.
  • analyse
    • Scan semblait avoir des problèmes de type similaires à ceux de la grande mémoire, mais avec tous les mécanismes de readLines. En bref, cela ne correspondait tout simplement pas à la facture cette fois.
FTWynn
la source
3
Si vos critères sont assez simples, vous pouvez probablement vous en tirer en utilisant sedet / ou awken créant une version réduite du CSV que vous pouvez lire directement. Puisqu'il s'agit plus d'une solution de contournement qu'une réponse, je vais le laisser en commentaire.
Hank Gay
Je suis d'accord avec Hank - vous devriez utiliser le bon outil pour le travail, et s'il s'agit d'un simple nettoyage / suppression de données de lignes / colonnes non pertinentes, des outils de flux de ligne de commande comme sort / sed / awk sont excellents et vont être beaucoup moins gourmands en ressources que R ou python - si vous donnez un exemple de votre format de fichiers, nous pourrions probablement donner un exemple
Aaron Statham
Génial. Faites-nous savoir ce que vous découvrez.
Shane
@Hank & Aaron: Je suis généralement partisan d'utiliser le bon outil pour le travail, mais étant donné que c'est sur une machine Windows au travail et que j'apprends R au fur et à mesure, j'ai pensé que ce serait un bon exercice pour renoncer aux meilleures pratiques et essayez ceci en tant que R-seulement si possible.
FTWynn
2
Pour référence future, consultez le package data.table R. La freadfonction est beaucoup plus rapide que read.table. Utilisez quelque chose comme x = fread(file_path_here, data.table=FALSE)pour le charger en tant data.framequ'objet.
paleo13

Réponses:

39

Mon essai avec readLines. Ce morceau de code crée csvavec des années sélectionnées.

file_in <- file("in.csv","r")
file_out <- file("out.csv","a")
x <- readLines(file_in, n=1)
writeLines(x, file_out) # copy headers

B <- 300000 # depends how large is one pack
while(length(x)) {
    ind <- grep("^[^;]*;[^;]*; 20(09|10)", x)
    if (length(ind)) writeLines(x[ind], file_out)
    x <- readLines(file_in, n=B)
}
close(file_in)
close(file_out)
Marek
la source
C'est presque exactement ce que je viens d'écrire. Je sens que ce sera également la meilleure réponse, étant donné les contraintes de mémoire, les types mixtes et les lignes mal formées.
FTWynn
10

Je ne suis pas un expert en la matière, mais vous pourriez envisager d'essayer MapReduce , ce qui reviendrait essentiellement à adopter une approche «diviser pour conquérir». R a plusieurs options pour cela, notamment:

  1. mapReduce (R pur)
  2. RHIPE (qui utilise Hadoop ); voir l'exemple 6.2.2 dans la documentation pour un exemple de sous-ensembles de fichiers

Alternativement, R fournit plusieurs packages pour traiter des données volumineuses qui sortent de la mémoire (sur le disque). Vous pourriez probablement charger l'ensemble de données dans un bigmemoryobjet et effectuer la réduction complètement dans R. Voir http://www.bigmemory.org/ pour un ensemble d'outils pour gérer cela.

Shane
la source
Bonne suggestion, mais je n'ai pas beaucoup d'expérience avec MapReduce et ses semblables. Je vais devoir lire à ce sujet.
FTWynn
bigmemorypeut être plus facile pour vous d'essayer en premier, dans ce cas.
Shane
10

Existe-t-il une manière similaire de lire dans les fichiers un morceau à la fois dans R?

Oui. La fonction readChar () lira un bloc de caractères sans supposer qu'ils sont terminés par null. Si vous souhaitez lire des données sur une ligne à la fois, vous pouvez utiliser readLines () . Si vous lisez un bloc ou une ligne, effectuez une opération, puis écrivez les données, vous pouvez éviter le problème de mémoire. Bien que si vous avez envie de déclencher une grande instance de mémoire sur l'EC2 d'Amazon, vous pouvez obtenir jusqu'à 64 Go de RAM. Cela devrait contenir votre fichier et beaucoup d'espace pour manipuler les données.

Si vous avez besoin de plus de vitesse, la recommandation de Shane d'utiliser Map Reduce est très bonne. Cependant, si vous choisissez d'utiliser une grande instance de mémoire sur EC2, vous devriez regarder le package multicœur pour utiliser tous les cœurs sur une machine.

Si vous souhaitez lire de nombreux gigs de données délimitées dans R, vous devriez au moins rechercher le package sqldf qui vous permet d'importer directement dans sqldf à partir de R, puis d'opérer sur les données depuis R. J'ai trouvé que sqldf en était un. des moyens les plus rapides d'importer des gigs de données dans R, comme mentionné dans cette question précédente .

JD Long
la source
Je vais garder une instance EC2 à l'esprit, mais pour le moment, je dois m'en tenir à mon bureau et c'est 2 Go de RAM. sqldf ressemble définitivement à ce que j'avais en tête. Cependant, il s'étouffe également sur les lignes mal formées (il devrait y avoir 17 colonnes, mais quelques milliers de lignes n'en ont qu'une). Cela nécessite-t-il une autre méthode de prétraitement, ou y a-t-il une option qui me manque?
FTWynn
6

Il existe un tout nouveau package appelé colbycol qui vous permet de lire uniquement les variables souhaitées à partir d'énormes fichiers texte:

http://colbycol.r-forge.r-project.org/

Il transmet tous les arguments à read.table, donc la combinaison devrait vous permettre de sous-ensemble assez étroitement.

Ari B. Friedman
la source
6

le ff package est un moyen transparent de traiter des fichiers volumineux.

Vous pouvez voir le site Web du package et / ou une présentation à ce sujet.

J'espère que ça aide

Ali
la source
5

Vous pouvez importer des données dans la base de données SQLite , puis utiliser RSQLite pour sélectionner des sous-ensembles.

Marek
la source
Un bon plan, mais puisque c'est essentiellement ce que sqldf fait dans les coulisses, je préférerais cela. À moins qu'il n'y ait une meilleure façon de gérer les lignes mal formées si vous utilisez RSQLite simple?
FTWynn
5

Qu'en est-il de l'utilisation readret duread_*_chunked famille?

Donc dans votre cas:

testfile.csv

County; State; Year; Quarter; Segment; Sub-Segment; Sub-Sub-Segment; GDP
Ada County;NC;2009;4;FIRE;Financial;Banks;80.1
Ada County;NC;2010;1;FIRE;Financial;Banks;82.5
lol
Ada County;NC;2013;1;FIRE;Financial;Banks;82.5

Code réel

require(readr)
f <- function(x, pos) subset(x, Year %in% c(2009, 2010))
read_csv2_chunked("testfile.csv", DataFrameCallback$new(f), chunk_size = 1)

Cela s'applique fà chaque bloc, en se souvenant des noms de colonne et en combinant les résultats filtrés à la fin. Voir?callback quelle est la source de cet exemple.

Cela se traduit par:

# A tibble: 2 × 8
      County State  Year Quarter Segment `Sub-Segment` `Sub-Sub-Segment`   GDP
*      <chr> <chr> <int>   <int>   <chr>         <chr>             <chr> <dbl>
1 Ada County    NC  2009       4    FIRE     Financial             Banks   801
2 Ada County    NC  2010       1    FIRE     Financial             Banks   825

Vous pouvez même augmenter chunk_sizemais dans cet exemple il n'y a que 4 lignes.

Rentrop
la source
4

Avez-vous pensé à la grande mémoire ? Vérifiez ceci et cela .

George Dontas
la source
Bonne idée. Je vais l'examiner.
FTWynn
3

Vous pouvez peut-être migrer vers MySQL ou PostgreSQL pour vous éviter les limitations de MS Access.

Il est assez facile de connecter R à ces systèmes avec un connecteur de base de données DBI (disponible sur CRAN).

Banquise
la source
Touche pour utiliser de meilleurs outils de base de données, mais comme cela impliquerait des tracas administratifs (je dois adorer ces règlements administratifs dans les grandes entreprises), j'essaie de m'en tenir à ce que j'ai. De plus, je vise le moins de conversions possible entre le fichier texte que je reçois.
FTWynn
3

scan () a à la fois un argument nlines et un argument skip. Y a-t-il une raison pour laquelle vous pouvez simplement utiliser cela pour lire un morceau de lignes à la fois, en vérifiant la date pour voir si elle est appropriée? Si le fichier d'entrée est trié par date, vous pouvez stocker un index qui vous indique ce que votre saut et vos nlines devraient être qui accéléreraient le processus à l'avenir.

Frankc
la source
Je vais le vérifier, mais le fichier n'est pas ordonné par quelque chose d'utile comme la date. Les fournisseurs semblent penser qu'il est plus important de trier par région dans laquelle se trouve un comté donné. /
Soupir
Je pense que vous avez mal compris sa proposition: lisez votre fichier morceau par morceau et extrayez uniquement les lignes dont vous avez besoin de chaque morceau. Les fichiers n'ont pas besoin d'être classés.
Karl Forner
1

De nos jours, 3,5 Go n'est tout simplement pas si gros, je peux accéder à une machine avec 244 Go de RAM (r3.8xlarge) sur le cloud Amazon pour 2,80 $ / heure. Combien d'heures vous faudra-t-il pour comprendre comment résoudre le problème à l'aide de solutions de type big data? Combien vaut votre temps? Oui, cela vous prendra une heure ou deux pour comprendre comment utiliser AWS - mais vous pouvez apprendre les bases sur un niveau gratuit, télécharger les données et lire les 10 000 premières lignes dans R pour vérifier que cela a fonctionné, puis vous pouvez lancer un grande instance de mémoire comme r3.8xlarge et tout lire! Juste mon 2c.

Sean
la source
0

Maintenant, 2017, je suggérerais d'opter pour Spark et SparkR.

  • la syntaxe peut être écrite d'une manière simple assez similaire à celle d'un dplyr

  • il s'adapte assez bien à une petite mémoire (petite au sens de 2017)

Cependant, cela peut être une expérience intimidante pour commencer ...

Ott Toomet
la source
-3

J'irais chercher une base de données, puis ferais quelques requêtes pour extraire les échantillons dont vous avez besoin via DBI

Veuillez éviter d'importer un fichier csv de 3,5 Go dans SQLite. Ou au moins, vérifiez que votre énorme base de données s'inscrit dans les limites de SQLite, http://www.sqlite.org/limits.html

C'est un sacré gros DB que vous avez. J'irais pour MySQL si vous avez besoin de vitesse. Mais soyez prêt à attendre de nombreuses heures pour que l'importation se termine. À moins que vous n'ayez du matériel non conventionnel ou que vous écriviez du futur ...

L'EC2 d'Amazon pourrait également être une bonne solution pour instancier un serveur exécutant R et MySQL.

mes deux modestes centimes valent.

Liborio Francesco Cannici
la source
18
Quelle est la taille de 3,5 Go pour sqlite? Tant que vous utilisez un système de fichiers approprié, il ne devrait y avoir aucun problème (j'utilise régulièrement des dbs sqlite> 30 Go pour les applications mono-utilisateur)
Aaron Statham