web-dev-qa-db-fra.com

Spécification des types de colonne lors de l'importation de données xlsx dans R avec package readxl

J'importe les tables xlsx 2007 dans R 3.2.1patched en utilisant le package readxl 0.1.0 sous Windows 7 64. La taille des tables est de l'ordre de 25 000 lignes sur 200 colonnes.

La fonction read_Excel() est un régal. Mon seul problème est son affectation de la classe de colonne (type de données) à des colonnes peu peuplées. Par exemple, une colonne donnée peut être NA pour 20 000 lignes, puis prendra une valeur de caractère pour la ligne 20 001. read_Excel() semble utiliser par défaut le type de colonne numérique lors de l'analyse des n premières lignes d'une colonne et lors de la recherche de NAs uniquement. Les données à l'origine du problème sont des caractères dans une colonne numérotée. Lorsque la limite d'erreur est atteinte, l'exécution s'interrompt. En fait, je veux les données dans les colonnes clairsemées, donc fixer une limite d'erreur plus élevée n'est pas une solution.

Je peux identifier les colonnes problématiques en examinant les avertissements émis. Et read_Excel() a une option pour affirmer le type de données d'une colonne en définissant l'argument col_types en fonction de la documentation du package: 

Soit NULL à deviner à partir de la feuille de calcul ou un vecteur de caractère contenant blank, numeric, date ou text.

Mais cela signifie-t-il que je dois construire un vecteur de longueur 200 peuplé dans presque toutes les positions avec blank et text dans une poignée de positions correspondant aux colonnes incriminées?

Il y a probablement moyen de le faire en quelques lignes de code R. Créez un vecteur de la longueur requise et remplissez-le avec blanks. Peut-être qu'un autre vecteur contenant les numéros des colonnes doit être forcé à text, puis ... Ou peut-être est-il possible d'appeler pour read_Excel() uniquement les colonnes pour lesquelles ses suppositions ne sont pas celles souhaitées.

J'apprécierais toutes les suggestions. 

Merci d'avance.

15
jackw19

J'ai rencontré un problème similaire.

Dans mon cas, des lignes et des colonnes vides ont été utilisées comme séparateurs. Et il y avait beaucoup de tableaux (avec différents formats) contenus dans les feuilles. Ainsi, les packages {openxlsx} et {readxl} ne conviennent pas dans cette situation, car openxlsx supprime les colonnes vides (et aucun paramètre ne permet de modifier ce comportement). Le package Readxl fonctionne comme vous l'avez décrit et certaines données peuvent être perdues.

En définitive, je pense que la meilleure solution, si vous souhaitez gérer automatiquement de grandes quantités de données Excel, consiste à lire les feuilles sans modifier le format "texte", puis à traiter les données.frames conformément à vos règles.

Cette fonction permet de lire les feuilles sans modification (grâce à @ jack-wasey):

loadExcelSheet<-function(Excel.file, sheet)
{
  require("readxl")
  sheets <- readxl::Excel_sheets(Excel.file)
  sheet.num <- match(sheet, sheets) - 1
  num.columns <- length(readxl:::xlsx_col_types(Excel.file, sheet =   sheet.num,
                                              nskip = 0, n = 1))

  return.sheet <- readxl::read_Excel(excel.file, sheet = sheet,
                                col_types = rep("text", num.columns),
                                col_names = F)
  return.sheet 
}
6
Stanislav

Nouvelle solution depuis la version readxl version 1.x:

La solution dans la réponse actuellement préférée ne fonctionne plus avec les versions plus récentes que la version 0.1.0 de readxl, car la fonction interne au package readxl:::xlsx_col_types utilisée n'existe plus.

La nouvelle solution consiste à utiliser le paramètre nouvellement introduit guess_max pour augmenter le nombre de lignes utilisées pour "deviner" le type de données approprié des colonnes:

read_Excel("My_Excel_file.xlsx", sheet = 1, guess_max = 1048576)

La valeur 1 048 576 est le nombre maximal de lignes prises en charge par Excel actuellement, voir les spécifications Excel: https://support.office.com/en-us/article/Excel-specifications-and-limits-1672b34d-7043-467e -8e27-269d656771c3

PS: Si vous vous souciez de performance utiliser toutes les lignes pour deviner le type de données: read_Excel semble lire le fichier une seule fois et devine en mémoire alors la pénalité de performance est très faible comparée à celle sauvegardée travail.

4
R Yoda

Cela dépend de la rareté de vos données à différents endroits dans différentes colonnes et de leur degré de rareté. J'ai trouvé qu'avoir plus de lignes n'améliorait pas l'analyse: la plupart étaient toujours vides et interprétées comme du texte, même si plus tard elles devenaient des dates, etc.

Une solution consiste à générer la première ligne de données de votre tableau Excel afin d'inclure des données représentatives pour chaque colonne et à l'utiliser pour deviner les types de colonnes. Je n'aime pas cela parce que je veux laisser les données d'origine intactes.

Une autre solution de contournement, si vous avez des lignes complètes quelque part dans la feuille de calcul, consiste à utiliser nskip au lieu de n. Cela donne le point de départ de la colonne qui devine. Disons que la ligne de données 117 contient un ensemble complet de données:

readxl:::xlsx_col_types(path = "a.xlsx", nskip = 116, n = 1)

Notez que vous pouvez appeler la fonction directement, sans avoir à la modifier dans l'espace de noms.

Vous pouvez ensuite utiliser le vecteur des types de feuille de calcul pour appeler read_Excel:

col_types <- readxl:::xlsx_col_types(path = "a.xlsx", nskip = 116, n = 1)
dat <- readxl::read_Excel(path = "a.xlsx", col_types = col_types)

Ensuite, vous pouvez mettre à jour manuellement les colonnes pour lesquelles il se trompe encore.

4
Jack Wasey

En examinant source , nous constatons qu’un appel Rcpp renvoie les types de colonne supposés:

xlsx_col_types <- function(path, sheet = 0L, na = "", nskip = 0L, n = 100L) {
    .Call('readxl_xlsx_col_types', PACKAGE = 'readxl', path, sheet, na, nskip, n)
}

Vous pouvez constater que par défaut, nskip = 0L, n = 100L vérifie les 100 premières lignes pour deviner le type de colonne. Vous pouvez changer nskip pour ignorer le texte de l'en-tête et augmenter n (au prix d'une exécution beaucoup plus lente) en procédant comme suit:

col_types <-  .Call( 'readxl_xlsx_col_types', PACKAGE = 'readxl', 
                     path = file_loc, sheet = 0L, na = "", 
                     nskip = 1L, n = 10000L )

# if a column type is "blank", no values yet encountered -- increase n or just guess "text"
col_types[col_types=="blank"] <- "text"

raw <- read_Excel(path = file_loc, col_types = col_types)

Sans regarder le fichier .Rcpp, je ne sais pas tout de suite si nskip = 0L saute la ligne d’en-tête (la ligne zéro dans la comptabilisation c ++) ou ne saute aucune ligne. J'ai évité l'ambiguïté en utilisant simplement nskip = 1L, car le fait de sauter une ligne de mon jeu de données n'a pas d'incidence sur les suppositions globales du type de colonne.

2
C8H10N4O2

En lisant le source, il semble que les types de colonnes soient devinés par les fonctions xls_col_types ou xlsx_col_types, qui sont implémentées dans Rcpp, mais ont les valeurs par défaut:

xls_col_types <- function(path, na, sheet = 0L, nskip = 0L, n = 100L, has_col_names = FALSE) {
    .Call('readxl_xls_col_types', PACKAGE = 'readxl', path, na, sheet, nskip, n, has_col_names)
}

xlsx_col_types <- function(path, sheet = 0L, na = "", nskip = 0L, n = 100L) {
    .Call('readxl_xlsx_col_types', PACKAGE = 'readxl', path, sheet, na, nskip, n)
}

Mon C++ est très rouillé, mais il semble que le n=100L soit la commande indiquant le nombre de lignes à lire.

Comme ce sont des fonctions non exportées, coller dans:

fixInNamespace("xls_col_types", "readxl")
fixInNamespace("xlsx_col_types", "readxl")

Et dans la fenêtre contextuelle, changez le n = 100L en un plus grand nombre. Relancez ensuite votre importation de fichier.

2
jeremycg

Les fonctions internes permettant de deviner les types de colonnes peuvent être définies sur un nombre quelconque de lignes à analyser. Mais read_Excel()n'implémente pas (encore?).

La solution ci-dessous est juste une réécriture de la fonction originale read_Excel() avec l'argument n_max qui utilise par défaut toutes les lignes. Par manque d'imagination, cette fonction étendue s'appelle read_Excel2

Il suffit de remplacer read_Excel par read_Excel2 pour évaluer les types de colonne par toutes les lignes.

# Inspiration: https://github.com/hadley/readxl/blob/master/R/read_Excel.R 
# Rewrote read_Excel() to read_Excel2() with additional argument 'n_max' for number
# of rows to evaluate in function readxl:::xls_col_types and
# readxl:::xlsx_col_types()
# This is probably an unstable solution, since it calls internal functions from readxl.
# May or may not survive next update of readxl. Seems to work in version 0.1.0
library(readxl)

read_Excel2 <- function(path, sheet = 1, col_names = TRUE, col_types = NULL,
                       na = "", skip = 0, n_max = 1050000L) {

  path <- readxl:::check_file(path)
  ext <- tolower(tools::file_ext(path))

  switch(readxl:::Excel_format(path),
         xls =  read_xls2(path, sheet, col_names, col_types, na, skip, n_max),
         xlsx = read_xlsx2(path, sheet, col_names, col_types, na, skip, n_max)
  )
}
read_xls2 <- function(path, sheet = 1, col_names = TRUE, col_types = NULL,
                     na = "", skip = 0, n_max = n_max) {

  sheet <- readxl:::standardise_sheet(sheet, readxl:::xls_sheets(path))

  has_col_names <- isTRUE(col_names)
  if (has_col_names) {
    col_names <- readxl:::xls_col_names(path, sheet, nskip = skip)
  } else if (readxl:::isFALSE(col_names)) {
    col_names <- paste0("X", seq_along(readxl:::xls_col_names(path, sheet)))
  }

  if (is.null(col_types)) {
    col_types <- readxl:::xls_col_types(
      path, sheet, na = na, nskip = skip, has_col_names = has_col_names, n = n_max
    )
  }

  readxl:::xls_cols(path, sheet, col_names = col_names, col_types = col_types, 
                    na = na, nskip = skip + has_col_names)
}

read_xlsx2 <- function(path, sheet = 1L, col_names = TRUE, col_types = NULL,
                       na = "", skip = 0, n_max = n_max) {
  path <- readxl:::check_file(path)
  sheet <-
    readxl:::standardise_sheet(sheet, readxl:::xlsx_sheets(path))

  if (is.null(col_types)) {
    col_types <-
      readxl:::xlsx_col_types(
        path = path, sheet = sheet, na = na, nskip = skip + isTRUE(col_names), n = n_max
      )
  }

  readxl:::read_xlsx_(path, sheet, col_names = col_names, col_types = col_types, na = na,
             nskip = skip)
}

Vous risquez d’avoir une mauvaise performance à cause de ces longues suppositions. Je n'ai pas encore essayé de grands ensembles de données, mais juste de petites données pour vérifier leur fonctionnement.

1
Stefan Jansson