web-dev-qa-db-fra.com

Rose des vents avec ggplot (R)?

Je recherche un bon code R (ou un package) qui utilise ggplot2 pour créer roses de vent et indiquant la fréquence, la magnitude et la direction des vents. 

Je suis particulièrement intéressé par ggplot2, car la construction de l'intrigue de cette façon me donne l'occasion d'exploiter le reste des fonctionnalités qu'il contient.

Données de test

Téléchargez une année de données météorologiques du niveau 80 mètres sur la tour "M2" de National Wind Technology. Ce lien créera un fichier .csv qui sera automatiquement téléchargé. Vous devez trouver ce fichier ("20130101.csv") et le lire.

# read in a data file
data.in <- read.csv(file = "A:/drive/somehwere/20130101.csv",
                    col.names = c("date","hr","ws.80","wd.80"),
                    stringsAsFactors = FALSE))

Cela fonctionnerait avec n’importe quel fichier .csv et écraserait les noms de colonnes.

Échantillon de données

Si vous ne souhaitez pas télécharger ces données, voici 10 points de données que nous utiliserons pour démontrer le processus:

data.in <- structure(list(date = structure(c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 

1L, 1L), .Label = "1/1/2013", class = "facteur"), h = 1: 9, ws.80 = c (5, 7, 7, 51,9, 11, 12, 9, 11, 17), wd.80 = c (30, 30, 30, 180, 180, 180, 269, 270, 271)), .Noms = c ("date", "hr", " ws.80 "," wd.80 "), row.names = c (NA, -9L), class =" data.frame ")

32
Andy Clifton

Par souci d'argumentation, supposons que nous utilisons le cadre de données data.in, qui comporte deux colonnes de données et une sorte d'informations de date/heure. Nous allons d'abord ignorer les informations de date et heure.

La fonction ggplot

J'ai codé la fonction ci-dessous. Je suis intéressé par l'expérience d'autres personnes ou des suggestions sur la façon d'améliorer cela.

# WindRose.R
require(ggplot2)
require(RColorBrewer)

plot.windrose <- function(data,
                      spd,
                      dir,
                      spdres = 2,
                      dirres = 30,
                      spdmin = 2,
                      spdmax = 20,
                      spdseq = NULL,
                      palette = "YlGnBu",
                      countmax = NA,
                      debug = 0){


# Look to see what data was passed in to the function
  if (is.numeric(spd) & is.numeric(dir)){
    # assume that we've been given vectors of the speed and direction vectors
    data <- data.frame(spd = spd,
                       dir = dir)
    spd = "spd"
    dir = "dir"
  } else if (exists("data")){
    # Assume that we've been given a data frame, and the name of the speed 
    # and direction columns. This is the format we want for later use.    
  }  

  # Tidy up input data ----
  n.in <- NROW(data)
  dnu <- (is.na(data[[spd]]) | is.na(data[[dir]]))
  data[[spd]][dnu] <- NA
  data[[dir]][dnu] <- NA

  # figure out the wind speed bins ----
  if (missing(spdseq)){
    spdseq <- seq(spdmin,spdmax,spdres)
  } else {
    if (debug >0){
      cat("Using custom speed bins \n")
    }
  }
  # get some information about the number of bins, etc.
  n.spd.seq <- length(spdseq)
  n.colors.in.range <- n.spd.seq - 1

  # create the color map
  spd.colors <- colorRampPalette(brewer.pal(min(max(3,
                                                    n.colors.in.range),
                                                min(9,
                                                    n.colors.in.range)),                                               
                                            palette))(n.colors.in.range)

  if (max(data[[spd]],na.rm = TRUE) > spdmax){    
    spd.breaks <- c(spdseq,
                    max(data[[spd]],na.rm = TRUE))
    spd.labels <- c(paste(c(spdseq[1:n.spd.seq-1]),
                          '-',
                          c(spdseq[2:n.spd.seq])),
                    paste(spdmax,
                          "-",
                          max(data[[spd]],na.rm = TRUE)))
    spd.colors <- c(spd.colors, "grey50")
  } else{
    spd.breaks <- spdseq
    spd.labels <- paste(c(spdseq[1:n.spd.seq-1]),
                        '-',
                        c(spdseq[2:n.spd.seq]))    
  }
  data$spd.binned <- cut(x = data[[spd]],
                         breaks = spd.breaks,
                         labels = spd.labels,
                         ordered_result = TRUE)
  # clean up the data
  data. <- na.omit(data)

  # figure out the wind direction bins
  dir.breaks <- c(-dirres/2,
                  seq(dirres/2, 360-dirres/2, by = dirres),
                  360+dirres/2)  
  dir.labels <- c(paste(360-dirres/2,"-",dirres/2),
                  paste(seq(dirres/2, 360-3*dirres/2, by = dirres),
                        "-",
                        seq(3*dirres/2, 360-dirres/2, by = dirres)),
                  paste(360-dirres/2,"-",dirres/2))
  # assign each wind direction to a bin
  dir.binned <- cut(data[[dir]],
                    breaks = dir.breaks,
                    ordered_result = TRUE)
  levels(dir.binned) <- dir.labels
  data$dir.binned <- dir.binned

  # Run debug if required ----
  if (debug>0){    
    cat(dir.breaks,"\n")
    cat(dir.labels,"\n")
    cat(levels(dir.binned),"\n")       
  }  

  # deal with change in ordering introduced somewhere around version 2.2
  if(packageVersion("ggplot2") > "2.2"){    
    cat("Hadley broke my code\n")
    data$spd.binned = with(data, factor(spd.binned, levels = rev(levels(spd.binned))))
    spd.colors = rev(spd.colors)
  }

  # create the plot ----
  p.windrose <- ggplot(data = data,
                       aes(x = dir.binned,
                           fill = spd.binned)) +
    geom_bar() + 
    scale_x_discrete(drop = FALSE,
                     labels = waiver()) +
    coord_polar(start = -((dirres/2)/360) * 2*pi) +
    scale_fill_manual(name = "Wind Speed (m/s)", 
                      values = spd.colors,
                      drop = FALSE) +
    theme(axis.title.x = element_blank())

  # adjust axes if required
  if (!is.na(countmax)){
    p.windrose <- p.windrose +
      ylim(c(0,countmax))
  }

  # print the plot
  print(p.windrose)  

  # return the handle to the wind rose
  return(p.windrose)
}

Preuve de concept et logique

Nous allons maintenant vérifier que le code fait ce que nous attendons. Pour cela, nous allons utiliser le simple jeu de données de démonstration.

# try the default settings
p0 <- plot.windrose(spd = data.in$ws.80,
                   dir = data.in$wd.80)

Cela nous donne cette intrigue:  Unit Test Results Donc: nous avons correctement regroupé les données en fonction de la direction et de la vitesse du vent et avons codé nos données hors plage comme prévu. Cela semble bon!

Utiliser cette fonction

Maintenant, nous chargeons les données réelles. Nous pouvons charger ceci à partir de l'URL:

data.in <- read.csv(file = "http://midcdmz.nrel.gov/apps/plot.pl?site=NWTC&start=20010824&edy=26&Emo=3&eyr=2062&year=2013&month=1&day=1&endyear=2013&endmonth=12&endday=31&time=0&inst=21&inst=39&type=data&wrlevel=2&preset=0&first=3&math=0&second=-1&value=0.0&user=0&axis=1",
                    col.names = c("date","hr","ws.80","wd.80"))

ou du fichier:

data.in <- read.csv(file = "A:/blah/20130101.csv",
                    col.names = c("date","hr","ws.80","wd.80"))

Le moyen rapide

La méthode simple pour utiliser cela avec les données M2 consiste à simplement passer des vecteurs séparés pour spd et dir (vitesse et direction):

# try the default settings
p1 <- plot.windrose(spd = data.in$ws.80,
                   dir = data.in$wd.80)

Ce qui nous donne cette parcelle:

 enter image description here

Et si nous voulons des bacs personnalisés, nous pouvons les ajouter comme arguments:

p2 <- plot.windrose(spd = data.in$ws.80,
                   dir = data.in$wd.80,
                   spdseq = c(0,3,6,12,20))

 enter image description here

Utilisation d'un bloc de données et des noms de colonnes

Pour rendre les tracés plus compatibles avec ggplot(), vous pouvez également transmettre un cadre de données et le nom des variables de vitesse et de direction:

p.wr2 <- plot.windrose(data = data.in,
              spd = "ws.80",
              dir = "wd.80")

Facettage par une autre variable

Nous pouvons également tracer les données par mois ou par an en utilisant les capacités de traitement de ggplot. Commençons par obtenir l'horodatage à partir des informations de date et heure dans data.in et en effectuant la conversion en mois et en année:

# first create a true POSIXCT timestamp from the date and hour columns
data.in$timestamp <- as.POSIXct(paste0(data.in$date, " ", data.in$hr),
                                tz = "GMT",
                                format = "%m/%d/%Y %H:%M")

# Convert the time stamp to years and months 
data.in$Year <- as.numeric(format(data.in$timestamp, "%Y"))
data.in$month <- factor(format(data.in$timestamp, "%B"),
                        levels = month.name)

Ensuite, vous pouvez appliquer une facette pour montrer comment la rose des vents varie selon les mois:

# recreate p.wr2, so that includes the new data
p.wr2 <- plot.windrose(data = data.in,
              spd = "ws.80",
              dir = "wd.80")
# now generate the faceting
p.wr3 <- p.wr2 + facet_wrap(~month,
                            ncol = 3)
# and remove labels for clarity
p.wr3 <- p.wr3 + theme(axis.text.x = element_blank(),
          axis.title.x = element_blank())

 enter image description here

Commentaires

Quelques remarques à propos de la fonction et de son utilisation:

  • Les entrées sont:
    • vecteurs de vitesse (spd) et de direction (dir) ou le nom du bloc de données et les noms des colonnes contenant les données de vitesse et de direction.
    • valeurs facultatives de la taille de la corbeille pour la vitesse du vent (spdres) et la direction (dirres). 
    • palette est le nom d'une palette colorbrewer sequential, 
    • countmax définit la portée de la rose des vents. 
    • debug est un commutateur (0,1,2) permettant d’activer différents niveaux de débogage.
  • Je voulais pouvoir définir la vitesse maximale (spdmax) et le nombre (countmax) des tracés pour pouvoir comparer les androïdes de différents ensembles de données.
  • Si la vitesse du vent dépasse (spdmax), elle est ajoutée en tant que région grise (voir la figure). Je devrais probablement coder quelque chose comme spdmin également, ainsi que les régions de code de couleur où la vitesse du vent est inférieure à celle.
  • À la suite d’une demande, j’ai mis en place une méthode d’utilisation de bacs de vitesse du vent personnalisés. Ils peuvent être ajoutés en utilisant l'argument spdseq = c(1,3,5,12).
  • Vous pouvez supprimer les étiquettes de bac à degrés en utilisant les commandes habituelles de ggplot pour effacer l'axe des x: p.wr3 + theme(axis.text.x = element_blank(),axis.title.x = element_blank()).
  • Récemment, ggplot2 a modifié la commande des bacs afin que les parcelles ne fonctionnent pas. Je pense que c'était la version 2.2. Mais, si vos graphiques semblent un peu bizarres, modifiez le code de sorte que le test de "2.2" soit peut-être "2.1" ou "2.0".
65
Andy Clifton

Voici ma version du code. J'ai ajouté des étiquettes pour les directions (N, NNE, NE, ENE, E ...) et j'ai créé l'étiquette y pour afficher la fréquence en pourcentage au lieu du nombre.

Cliquez ici pour voir la figure du vent Rose avec les directions et la fréquence (%)

    # WindRose.R
require(ggplot2)
require(RColorBrewer)
require(scales)

plot.windrose <- function(data,
                          spd,
                          dir,
                          spdres = 2,
                          dirres = 22.5,
                          spdmin = 2,
                          spdmax = 20,
                          spdseq = NULL,
                          palette = "YlGnBu",
                          countmax = NA,
                          debug = 0){


  # Look to see what data was passed in to the function
  if (is.numeric(spd) & is.numeric(dir)){
    # assume that we've been given vectors of the speed and direction vectors
    data <- data.frame(spd = spd,
                       dir = dir)
    spd = "spd"
    dir = "dir"
  } else if (exists("data")){
    # Assume that we've been given a data frame, and the name of the speed 
    # and direction columns. This is the format we want for later use.    
  }  

  # Tidy up input data ----
  n.in <- NROW(data)
  dnu <- (is.na(data[[spd]]) | is.na(data[[dir]]))
  data[[spd]][dnu] <- NA
  data[[dir]][dnu] <- NA

  # figure out the wind speed bins ----
  if (missing(spdseq)){
    spdseq <- seq(spdmin,spdmax,spdres)
  } else {
    if (debug >0){
      cat("Using custom speed bins \n")
    }
  }
  # get some information about the number of bins, etc.
  n.spd.seq <- length(spdseq)
  n.colors.in.range <- n.spd.seq - 1

  # create the color map
  spd.colors <- colorRampPalette(brewer.pal(min(max(3,
                                                    n.colors.in.range),
                                                min(9,
                                                    n.colors.in.range)),                                               
                                            palette))(n.colors.in.range)

  if (max(data[[spd]],na.rm = TRUE) > spdmax){    
    spd.breaks <- c(spdseq,
                    max(data[[spd]],na.rm = TRUE))
    spd.labels <- c(paste(c(spdseq[1:n.spd.seq-1]),
                          '-',
                          c(spdseq[2:n.spd.seq])),
                    paste(spdmax,
                          "-",
                          max(data[[spd]],na.rm = TRUE)))
    spd.colors <- c(spd.colors, "grey50")
  } else{
    spd.breaks <- spdseq
    spd.labels <- paste(c(spdseq[1:n.spd.seq-1]),
                        '-',
                        c(spdseq[2:n.spd.seq]))    
  }
  data$spd.binned <- cut(x = data[[spd]],
                         breaks = spd.breaks,
                         labels = spd.labels,
                         ordered_result = TRUE)

  # figure out the wind direction bins
  dir.breaks <- c(-dirres/2,
                  seq(dirres/2, 360-dirres/2, by = dirres),
                  360+dirres/2)  
  dir.labels <- c(paste(360-dirres/2,"-",dirres/2),
                  paste(seq(dirres/2, 360-3*dirres/2, by = dirres),
                        "-",
                        seq(3*dirres/2, 360-dirres/2, by = dirres)),
                  paste(360-dirres/2,"-",dirres/2))
  # assign each wind direction to a bin
  dir.binned <- cut(data[[dir]],
                    breaks = dir.breaks,
                    ordered_result = TRUE)
  levels(dir.binned) <- dir.labels
  data$dir.binned <- dir.binned

  # Run debug if required ----
  if (debug>0){    
    cat(dir.breaks,"\n")
    cat(dir.labels,"\n")
    cat(levels(dir.binned),"\n")

  }  

  # create the plot ----
  p.windrose <- ggplot(data = data,
                       aes(x = dir.binned,
                           fill = spd.binned
                           ,y = (..count..)/sum(..count..)
                           ))+
    geom_bar() + 
    scale_x_discrete(drop = FALSE,
                     labels = c("N","NNE","NE","ENE", "E", 
                                "ESE", "SE","SSE", 
                                "S","SSW", "SW","WSW", "W", 
                                "WNW","NW","NNW")) +
    coord_polar(start = -((dirres/2)/360) * 2*pi) +
    scale_fill_manual(name = "Wind Speed (m/s)", 
                      values = spd.colors,
                      drop = FALSE) +
    theme(axis.title.x = element_blank()) + 
    scale_y_continuous(labels = percent) +
    ylab("Frequencia")

  # adjust axes if required
  if (!is.na(countmax)){
    p.windrose <- p.windrose +
      ylim(c(0,countmax))
  }

  # print the plot
  print(p.windrose)  

  # return the handle to the wind rose
  return(p.windrose)
}

Avez-vous déjà essayé la fonction windRose du package Openair? C'est très facile et vous pouvez définir des intervalles, des statistiques, etc.

windRose(mydata, ws = "ws", wd = "wd", ws2 = NA, wd2 = NA, 
  ws.int = 2, angle = 30, type = "default", bias.corr = TRUE, cols
  = "default", grid.line = NULL, width = 1, seg = NULL, auto.text 
  = TRUE, breaks = 4, offset = 10, normalise = FALSE, max.freq = 
  NULL, Paddle = TRUE, key.header = NULL, key.footer = "(m/s)", 
  key.position = "bottom", key = TRUE, Dig.lab = 5, statistic = 
  "prop.count", pollutant = NULL, annotate = TRUE, angle.scale = 
  315, border = NA, ...)


  pollutionRose(mydata, pollutant = "nox", key.footer = pollutant,
  key.position = "right", key = TRUE, breaks = 6, Paddle = FALSE, 
  seg = 0.9, normalise = FALSE, ...)
1
Carolina Maués