4
votes

Défi d'efficacité R: diviser un long vecteur de caractères

Le problème est d'analyser efficacement les données de ce format:

data.frame(
    position = str_match_all(lineup, "\\s[0-9A-Z]{1,2}\\s")[[1]] %>% as.character() %>% str_trim(),
    player = str_split(lineup, "\\s[0-9A-Z]{1,2}\\s")[[1]][-1],
    stringsAsFactors = F
)

dans un dataframe de deux colonnes; un pour le poste et un pour le joueur.

Les noms sont des joueurs de baseball, et chaque nom est précédé de leur position, qui est l'ensemble exact {C, P, P, OF, 3B, SS , 1B, OF, 2B, OF} dans un certain ordre. Autrement dit, ces positions exactes se produisent toujours.

Par exemple, "C James McCann" devrait se transformer en

data.frame(position = "C", player = "James McCann")

En réalité, j'ai plusieurs centaines de des milliers de ces chaînes, et je veux les analyser efficacement. Voici ma solution inefficace:

lineup = " C James McCann P Robbie Ray P Rafael Montero OF Giancarlo Stanton 3B Derek Dietrich SS Miguel Rojas 1B Tommy Joseph OF Marcell Ozuna 2B C?sar Hern?ndez OF Christian Yelich"

Cette solution tidyverse est simple, mais je suppose que je peux faire beaucoup mieux. Quelqu'un a-t-il des idées?


5 commentaires

"C James McCann" a-t-il été abandonné exprès? Il n'en fait pas partie votre solution tidyverse .


Est-il garanti que personne n'a le prénom ou le nom de famille identique à un poste?


@Maurits, non, désolé. Je l'ai édité maintenant


@qwr, c'est garanti. Partout où il y a l'initiale d'un joueur, elle serait écrite avec un point


@ThanksABundle Pouvez-vous fournir un exemple plus représentatif pour tester les cas extrêmes. Par exemple, pour le moment, chaque joueur a un prénom et un nom. Je suppose que vous pouvez aussi avoir des joueurs avec des prénoms? Qu'en est-il des noms de famille à double barillet? Vous mentionnez les initiales du joueur. Pour garantir la robustesse d'une méthode, il sera important de disposer d'un échantillon de données plus complexe avec lequel travailler.


3 Réponses :


3
votes

Vous pouvez créer un modèle unique qui vous donnerait à la fois la position et le nom du joueur avec stringi :: stri_match_all_regex:

lapply( results, function(x){ as.data.frame(x[ , -1]) })
[[1]]
  V1                V2
1  C      James McCann
2  P        Robbie Ray
3  P    Rafael Montero
4 OF Giancarlo Stanton
5 3B    Derek Dietrich
6 SS      Miguel Rojas
7 1B      Tommy Joseph
8 OF     Marcell Ozuna
9 OF  Christian Yelich

J'ai rendu le modèle plus restrictif que le vôtre, car le mien limite le une ou deux lettres entre les espaces aux seules combinaisons correspondant aux positions de baseball. Vous allez obtenir une liste avec des éléments qui sont des matrices pour chaque ligne. Vous devriez probablement publier un exemple plus complexe pour prendre en charge le traitement ultérieur qui sera nécessaire. Vous devrez utiliser quelque chose du type lapply (results, function (x) {as.data.frame (x [ -1])})

stri_match_all_regex(lineup, 
                   patt= "(C|P|OF|3B|SS|1B|OF|2B) ([A-Z][A-Za-z]+ [A-Z][A-Za-z]+)" )
[[1]]
      [,1]                   [,2] [,3]               
 [1,] "C James McCann"       "C"  "James McCann"     
 [2,] "P Robbie Ray"         "P"  "Robbie Ray"       
 [3,] "P Rafael Montero"     "P"  "Rafael Montero"   
 [4,] "OF Giancarlo Stanton" "OF" "Giancarlo Stanton"
 [5,] "3B Derek Dietrich"    "3B" "Derek Dietrich"   
 [6,] "SS Miguel Rojas"      "SS" "Miguel Rojas"     
 [7,] "1B Tommy Joseph"      "1B" "Tommy Joseph"     
 [8,] "OF Marcell Ozuna"     "OF" "Marcell Ozuna"    
 [9,] "OF Christian Yelich"  "OF" "Christian Yelich" 


0 commentaires

2
votes

Voici une option stringr :: str_split , utilisant un look-behind et un look-ahead positifs

pos <- c("C", "P", "P", "OF", "3B", "SS", "1B", "OF", "2B", "OF")
pat <- sprintf("(%s)", paste(pos, collapse = "|"))

library(stringr)
matrix(unlist(str_split(trimws(lineup), sprintf(
    "((?<=(%s))\\s|\\s(?=(%s)))", pat, pat))), ncol = 2, byrow = T)
#    [,1] [,2]
#[1,] "C"  "James McCann"
#[2,] "P"  "Robbie Ray"
#[3,] "P"  "Rafael Montero"
#[4,] "OF" "Giancarlo Stanton"
#[5,] "3B" "Derek Dietrich"
#[6,] "SS" "Miguel Rojas"
#[7,] "1B" "Tommy Joseph"
#[8,] "OF" "Marcell Ozuna"
#[9,] "2B" "C?sar Hern?ndez"
#[10,] "OF" "Christian Yelich"

Je ne sais pas dans quelle mesure cela couvre tout cas de bord. Un exemple de chaîne plus complexe et représentatif serait utile pour les tests.


0 commentaires

2
votes

Voici une solution qui convertit lineup en une chaîne au format de fichier csv qui est ensuite lue par fread () :

# original
lineup = " C James McCann P Robbie Ray P Rafael Montero OF Giancarlo Stanton 3B Derek Dietrich SS Miguel Rojas 1B Tommy Joseph OF Marcell Ozuna 2B C?sar Hern?ndez OF Christian Yelich"

# other use cases
lineup1 = "C James McCann P Robbie Ray P Rafael Montero OF Giancarlo Stanton 3B Derek Dietrich SS Miguel Rojas 1B Tommy Joseph OF Marcell Ozuna 2B C?sar Hern?ndez OF Christian Yelich"
lineup2 = " C James P. McCann P Robbie Ray, Jr P Rafael D Montero OF Giancarlo Stanton 3B Derek Dietrich SS Miguel Rojas 1B Tommy Joseph OF Marcell Ozuna 2B C?sar Hern?ndez OF Christian Yelich"
lineup2a = " C James P. McCann P Robbie Ray P Rafael Montero OF Giancarlo Stanton 3B Derek Dietrich SS Miguel Rojas 1B Tommy Joseph OF Marcell Ozuna 2B C?sar Hern?ndez OF Christian Yelich"
lineup2b = " C James McCann P Robbie Ray, Jr P Rafael Montero OF Giancarlo Stanton 3B Derek Dietrich SS Miguel Rojas 1B Tommy Joseph OF Marcell Ozuna 2B C?sar Hern?ndez OF Christian Yelich"
lineup3 = "C James P. McCann P Robbie Ray, Jr P Rafael Montero OF Giancarlo Stanton 3B Derek Dietrich SS Miguel Rojas 1B Tommy Joseph OF Marcell Ozuna 2B C?sar Hern?ndez OF Christian Yelich"
lineup4 = " C James P. McCann P Robbie Ray; Jr P Rafael Montero OF Giancarlo Stanton 3B Derek Dietrich SS Miguel Rojas 1B Tommy Joseph OF Marcell Ozuna 2B C?sar Hern?ndez OF Christian Yelich"
    position            player
 1:        C   James P. McCann
 2:        P    Robbie Ray, Jr
 3:        P    Rafael Montero
 4:       OF Giancarlo Stanton
 5:       3B    Derek Dietrich
 6:       SS      Miguel Rojas
 7:       1B      Tommy Joseph
 8:       OF     Marcell Ozuna
 9:       2B   C?sar Hern?ndez
10:       OF  Christian Yelich

Le "truc" est de mettre un saut de ligne devant les caractères de position et un séparateur de colonne après, par exemple, "C" devient "\ nC;" .

lineup3 %T>% 
  {stopifnot(!stringr::str_detect(., ";"))} %>% 
  stringr::str_replace_all("(^\\s?|\\s)(C|P|OF|SS|1B|2B|3B)\\s", "\\\n\\2;") %>% 
  data.table::fread(header = FALSE, col.names = c("position", "player"))

retours

    position            player
 1:        C   James P. McCann
 2:        P    Robbie Ray, Jr
 3:        P  Rafael D Montero
 4:       OF Giancarlo Stanton
 5:       3B    Derek Dietrich
 6:       SS      Miguel Rojas
 7:       1B      Tommy Joseph
 8:       OF     Marcell Ozuna
 9:       2B   C?sar Hern?ndez
10:       OF  Christian Yelich

Cette approche ne fait pas beaucoup d'hypothèses sur les noms. Il fonctionne même avec des noms comme James P. McCann ou Robbie Ray, Jr .

lineup2 %>% 
  stringr::str_replace_all("\\s(C|P|OF|SS|1B|2B|3B)\\s", "\\\n\\1;") %>% 
  data.table::fread(header = FALSE, col.names = c("position", "player"))
[1] "\nC;James McCann\nP;Robbie Ray\nP;Rafael Montero\nOF;Giancarlo  Stanton\n3B;Derek Dietrich\nSS;Miguel Rojas\n1B;Tommy Joseph\nOF;Marcell Ozuna\n2B;C?sar Hern?ndez\nOF;Christian Yelich"

Trois conditions préalables doivent être remplies:

  1. La partie du nom ne doit pas contenir d'initiales qui sont également utilisées comme indicateurs de position, par exemple, les initiales C et P doivent être complétées par un point pour éviter toute confusion.
  2. Le séparateur de colonne ; ne doit pas être utilisé ailleurs dans la lineeup .
  3. La chaîne doit commencer par un espace au début.

La condition 3 peut être agitée avec une expression régulière améliorée et la condition 2 peut être vérifiée:

lineup %>% 
  stringr::str_replace_all("\\s(C|P|OF|SS|1B|2B|3B)\\s", "\\\n\\1;")
    position            player
 1:        C      James McCann
 2:        P        Robbie Ray
 3:        P    Rafael Montero
 4:       OF Giancarlo Stanton
 5:       3B    Derek Dietrich
 6:       SS      Miguel Rojas
 7:       1B      Tommy Joseph
 8:       OF     Marcell Ozuna
 9:       2B   C?sar Hern?ndez
10:       OF  Christian Yelich

Données

library(magrittr)  # piping used to improve readability
lineup %>% 
  stringr::str_replace_all("\\s(C|P|OF|SS|1B|2B|3B)\\s", "\\\n\\1;") %>% 
  data.table::fread(header = FALSE, col.names = c("position", "player"))


0 commentaires