Refactor a for loop of Active Users to improve speed - r

I have a dataframe of Users with their DATE_CREATION and DATE_DELETION.
ACTIVE_USERS <- data.frame(Date=c("2022-01-12", "2022-02-18", "2022-03-22", "2022-04-10", "2022-07-15" ) ,
USER_ID=c("user123","user311","user245","user245","user213"),
DATE_DELETION=c("2022-04-11","2022-04-12","2022-03-28","2022-07-12","2022-08-11"))
I am trying to create a graph that will show Nb of Active Users each day (we consider Active if simply existing).
For that, I wrote the below script that seems to do the job, but is extremely slow and takes a few hours to render (because I have much more dates than in above dataframe example). Any help to improve the speed of this script is welcomed, see below my current method:
I created a dataframe sequence of Dates from 1st date of creation to last, and I joined these two dataframes by Date of Creation
Axis_X_Date <- data.frame(seq(as.Date(min(ACTIVE_USERS$DATE_CREATION)),as.Date(max(ACTIVE_USERS$DATE_CREATION)),by = 1))
colnames(Axis_X_Date) <- c("DATE_CREATION")
Axis_X_Date <- merge(Axis_X_Date, ACTIVE_USERS, by = "DATE_CREATION", all.x =TRUE , all.y = FALSE)
ACTIVE_USERS <- Axis_X_Date
rm(Axis_X_Date)
colnames(ACTIVE_USERS) <- c("DATE_CREATION", "USER_ID", "DATE_DELETION")
Then I calculate the "Age" for each day.
ACTIVE_USERS$Age_Days <- ACTIVE_USERS$DATE_DELETION - ACTIVE_USERS$Date
And then I create a for loop, and apply the following actions for each row of the dataframe:
table_page_all <- data.frame()
for(i in 1:nrow(ACTIVE_USERS)){
# To simplify, I convert NAs of DATE_DELETION, meaning still active accounts, as active until today
ifelse( is.na(ACTIVE_USERS$DATE_DELETION[i] ), ACTIVE_USERS$DATE_DELETION[i] <- today_date, ACTIVE_USERS$DATE_DELETION[i])
# here I replicate USER_ID for each day where USER_ID was alive/active
Dates_Active <- seq(ACTIVE_USERS$Date[i], ACTIVE_USERS$DATE_DELETION[i], by="days")
Ref_ID <- rep(ACTIVE_USERS$USER_ID[i], length(Dates_Active))
# I combine these USER_ID and Dates_Active for each row of the ACTIVE_USERS dataframe
data_frame <- data.frame(Dates_Active, Ref_ID)
table_page_all <- rbind(table_page_all, data_frame)
cat('Row: ', i , 'out of', nrow(ACTIVE_USERS), '\n')
}

Related

Loop in data frames rows to create another data frame

I've got the following data frame:
Loans <- data.frame(
ID = c("215781","832567","721536"),
From = c("01-01-2023","04-15-2022","09-23-2021"),
End = c("05-02-2023","10-15-2023","12-23-2021"),
Type = c("Monthly","Quarterly","Monthly"))
I need to create another data frame which has for every intra-period of each Loan a row with the ID and the Date, this loop that I've made isn't right but gets the idea of what I wanted to do. It works for 1 row if you delete the loop part
library(bizdays)
Base <- data.frame("TM",today())
colnames(Base) <- c("TM","InterestDates")
for (i in Loans[i,]){
df <- as.data.frame(seq.Date(Loans$From,Loans$Until,by="month"))
colnames(df) <- "InterestDates"
df$TM <- Loans$TM
Base <- rbind(Base,df)
}
Something like this would be the expected output
ID | InterestDates
250414 | 2022-05-16
250414 | 2022-06-16
250414 | 2022-07-18
250414 | 2022-08-16
So I'm guessing you'd want something like this:
library(bizdays)
Loans <- data.frame(
ID = c("215781","832567","721536"),
From = c("01-01-2023","04-15-2022","09-23-2021"),
End = c("05-02-2023","10-15-2023","12-23-2021"),
Type = c("Monthly","Quarterly","Monthly"))
Base <- data.frame("ID" = character(),"InterestDates" = character())
Loans$From <- as.Date(Loans$From,format = "%m-%d-%y")
Loans$End <- as.Date(Loans$End,format = "%m-%d-%y")
for (i in 1:nrow(Loans)){
if(Loans$Type[i] == "Monthly"){
seq_dates <- seq.Date(Loans$From[i],Loans$End[i],by="month")
}else if(Loans$Type[i] == "Quarterly"){
seq_dates <- seq.Date(Loans$From[i],Loans$End[i],by="quarter")
}
df <- data.frame("ID" = rep(Loans$ID[i],length(seq_dates)),"InterestDates" = seq_dates)
Base <- rbind(Base,df)
}
There's several issues in your original code.
Base <- data.frame("TM",today()) makes a dataframe already with one entry, not an empty dataframe
The columns From and End of the Loans dataframe are not in date format that is necessary for the seq.Date command
Loans does not have a column TM, but I'm guessing from your output, you want the ID column anyway
Loans[i,] does not work since i does not exist - you cannot define i by i. Please look into how loops work in R
The loop index is never used inside the loop. If you want the i-th entry of a column of a dataframe, access it via Loans$From[i]
Also I'm not quite sure: Do you want by = "month" for every entry of your original dataframe? Or dependent on the Type column of the Loans dataframe?

Remove words per year in a corpus

I am working with a corpus with speeches spanning several years (aggregated to person-year level). I want to remove words that occur less than 4 times in a year (not remove it for the whole corpus, but only for the year in which it does not meet the threshold).
I have tried the following:
DT$text <- ifelse(grepl("1998", DT$session), mgsub(DT$text, words_remove_1998, ""), DT$text)
and
DT$text <- ifelse(grepl("1998", DT$session), str_remove_all(DT$text, words_remove_1998), DT$text)
and
DT$text <- ifelse(grepl("1998", DT$session), removeWords(DT$text, words_remove_1998), DT$text)
and
DT$text <- ifelse(grepl("1998", DT$session), drop_element(DT$text, words_remove_1998), DT$text)
However, none seem to work. Mgsub just substitutes the whole speech with "" for 1998, whilst the other options give error messages. The reason that removeWords does not work is that my words_remove_1998 vector is too large. I have tried to split the word vector and loop over the words (see code below), but R does not appear to like this (running forever).
group <- 100
n <- length(words_remove_1998)
r <- rep(1:ceiling(n/group),each=group)[1:n]
d <- split(words_remove_1998,r)
for (i in 1:length(d)) {
DT$text <- ifelse(grepl("1998", DT$session), removeWords(DT$text, c(paste(d[[i]]))), DT$text)
}
Any suggestions for how to solve this?
Thank you for your help!
Reproducible example:
text <- rbind(c("i like ice cream"), c("banana ice cream is my favourite"), c("ice cream is not my thing"))
name <- rbind(c("Arnold Ford"), c("Arnold Ford"), c("Leslie King"))
session <- rbind("1998", "1999", "1998")
DT <- cbind(name, session, text)
words_remove_1998 <- c("like", "ice", "cream")
newtext <- rbind(c("i"), c("banana ice cream is my favourite"), c("is not my thing"))
DT <- cbind(DT, newtext)
My real word vector that I want removed contains 30k elements.
I ended up not using any wrappings, as none of them could handle the size of the data. Insted I did it the old-fashioned and simple way; separate the text into several rows, count the occurences of each word per session (year) and person, then remove the rows corresponding to less than a threshold (same limit as I used to identify the vector with words I wanted to remove). Lastly, I aggregate the data back to it's initial level (person-year).
This only words because I am removing words according to a threshold. If I had a list of words to remove that I could not remove in this way, I would have been in more trouble.
DT_separate <- separate_rows(DT, text)
df <- DT_separate %>%
dplyr::group_by(session, text) %>%
dplyr::mutate(count = dplyr::n())
df <- df[df$count >5, ]
df <- aggregate(
text ~ x, #where x is a person-year id
data=df,
FUN=paste, collapse=' '
)
names(df)[names(df) == 'text'] <- 'text2'
DT <- left_join(DT, df, by="x")
DT$text <- DT$text2
DT <- DT[, !(colnames(DT) %in% c("text2"))]

apply inside apply function?

I've a data frame with the start and end of each month of the year 2019.
I need to make a fetch to an API, write a CSV file with name mydf plus month (eg. mydf-01.csv, mydf-02.csv, etc).
I need to fetch the data, write CSV, clean memory to avoid error message "not enough memory", and continue with the next month.
For now I've this, but is giving me error: not enough memory, because the expected data for all 2019 is around 3GB.
I was thinking on making a for loop. But maybe I can use another apply family function?
Months: my_dates data.frame
This is how it looks:
from to
2019-01-01 2019-01-31
2019-02-01 2019-02-28
2019-03-01 2019-03-31
...
Code to generate the 12 months:
som <- function(x) as.Date(cut(as.Date(x), "month")) # start of month
eom <- function(x) som(som(x) + 32) - 1 # end of month
month_ranges <- function(from, to) {
s <- seq(som(from), as.Date(to), "month")
data.frame(from = pmax(as.Date(from), s), to = pmin(as.Date(to), eom(s)))
}
my_dates <- month_ranges(som("2019-01-01"), eom("2019-12-31"))
Code to fetch data:
Currently it fetches all months, holds them in memory and at the end
it rbinds them together. However, this approache gives error when
months range is too large because data is above 2GB. So I'd like it for each month to save the data to > a CSV and continue to the next month.
library(googleAuthR)
library(googleAnalyticsR)
my_fetch <- function(ga_id, d1, d2) {
google_analytics(ga_id,
date_range = c(d1, d2),
metrics = c("totalEvents"),
dimensions = c("ga:date", "ga:eventCategory", "ga:eventAction", "ga:eventLabel"),
anti_sample = TRUE,
anti_sample_batches = 1,
rows_per_call = 400)
}
my_fetches_fetches <- mapply(my_fetch, myviewID, my_dates$from, my_dates$to, SIMPLIFY = FALSE)
total <- do.call(rbind, my_fetches_fetches)
UPDATE 1:
Maybe it could be possible to pass the "loop" that generates an error, like API timeout to continue to the next month?

Changing Dates in R from webscraper but not able to convert

I am trying to complete a problem that pulls from two data sets that need to be combined into one data set. To get to this point, I need to rbind both data sets by the year-month information. Unfortunately, the first data set needs to be tallied by year-month info, and I can't seem to figure out how to change the date so I can have month-year info rather than month-day-year info.
This is data on avalanches and I need to write code totally the number of avalanches each moth for the Snow Season, defined as Dec-Mar. How do I do that?
I keep trying to convert the format of the date to month-year but after I change it with
as.Date(avalancheslc$Date, format="%y-%m")
all the values for Date turn to NA's....help!
# write the webscraper
library(XML)
library(RCurl)
avalanche<-data.frame()
avalanche.url<-"https://utahavalanchecenter.org/observations?page="
all.pages<-0:202
for(page in all.pages){
this.url<-paste(avalanche.url, page, sep=" ")
this.webpage<-htmlParse(getURL(this.url))
thispage.avalanche<-readHTMLTable(this.webpage, which=1, header=T)
avalanche<-rbind(avalanche,thispage.avalanche)
}
# subset the data to the Salt Lake Region
avalancheslc<-subset(avalanche, Region=="Salt Lake")
str(avalancheslc)
avalancheslc$monthyear<-format(as.Date(avalancheslc$Date),"%Y-%m")
# How can I tally the number of avalanches?
The final output of my dataset should be something like:
date avalanches
2000-1 18
2000-2 4
2000-3 10
2000-12 12
2001-1 52
This should work (I tried it on only 1 page, not all 203). Note the use of the option stringsAsFactors = F in the readHTMLTable function, and the need to add names because 1 column does not automatically get one.
library(XML)
library(RCurl)
library(dplyr)
avalanche <- data.frame()
avalanche.url <- "https://utahavalanchecenter.org/observations?page="
all.pages <- 0:202
for(page in all.pages){
this.url <- paste(avalanche.url, page, sep=" ")
this.webpage <- htmlParse(getURL(this.url))
thispage.avalanche <- readHTMLTable(this.webpage, which = 1, header = T,
stringsAsFactors = F)
names(thispage.avalanche) <- c('Date','Region','Location','Observer')
avalanche <- rbind(avalanche,thispage.avalanche)
}
avalancheslc <- subset(avalanche, Region == "Salt Lake")
str(avalancheslc)
avalancheslc <- mutate(avalancheslc, Date = as.Date(Date, format = "%m/%d/%Y"),
monthyear = paste(year(Date), month(Date), sep = "-"))

yahoo tickers, time zone, and merging

I would like to download daily data from yahoo for the S&P 500, the DJIA, and 30-year T-Bonds, map the data to the proper time zone, and merge them with my own data. I have several questions.
My first problem is getting the tickers right. From yahoo's website, it looks like the tickers are: ^GSPC, ^DJI, and ^TYX. However, ^DJI fails. Any idea why?
My second problem is that I would like to constrain the time zone to GMT (I would like to ensure that all my data is on the same clock, GMT seems like a neutral choice), but I couldn' get it to work.
My third problem is that I would like to merge the yahoo data with my own data, obtained by other means and available in a different format. It is also daily data.
Here is my attempt at constraining the data to the GMT time zone. Executed at the top of my R script.
Sys.setenv(TZ = "GMT")
# > Sys.getenv("TZ")
# [1] "GMT"
# the TZ variable is properly set
# but does not affect the time zone in zoo objects, why?
Here is my code to get the yahoo data:
library("tseries")
library("xts")
date.start <- "1999-12-31"
date.end <- "2013-01-01"
# tickers <- c("GSPC","TYX","DJI")
# DJI Fails, why?
# http://finance.yahoo.com/q?s=%5EDJI
tickers <- c("GSPC","TYX") # proceed without DJI
z <- zoo()
index(z) <- as.Date(format(time(z)),tz="")
for ( i in 1:length(tickers) )
{
cat("Downloading ", i, " out of ", length(tickers) , "\n")
x <- try(get.hist.quote(
instrument = paste0("^",tickers[i])
, start = date.start
, end = date.end
, quote = "AdjClose"
, provider = "yahoo"
, origin = "1970-01-01"
, compression = "d"
, retclass = "zoo"
, quiet = FALSE )
, silent = FALSE )
print(x[1:4]) # check that it's not empty
colnames(x) <- tickers[i]
z <- try( merge(z,x), silent = TRUE )
}
Here is the dput(head(df)) of my dataset:
df <- structure(list(A = c(-0.011489000171423, -0.00020300000323914,
0.0430639982223511, 0.0201549995690584, 0.0372899994254112, -0.0183669999241829
), B = c(0.00110999995376915, -0.000153000000864267, 0.0497750006616116,
0.0337960012257099, 0.014121999964118, 0.0127800004556775), date = c(9861,
9862, 9863, 9866, 9867, 9868)), .Names = c("A", "B", "date"
), row.names = c("0001-01-01", "0002-01-01", "0003-01-01", "0004-01-01",
"0005-01-01", "0006-01-01"), class = "data.frame")
I'd like to merge the data in df with the data in z. I can't seem to get it to work.
I am new to R and very much open to your advice about efficiency, best practice, etc.. Thanks.
EDIT: SOLUTIONS
On the first problem: following GSee's suggestions, the Dow Jones Industrial Average data may be downloaded with the quantmod package: thus, instead of the "^DJI" ticker, which is no longer available from yahoo, use the "DJIA" ticker. Note that there is no caret in the "DJIA" ticker.
On the second problem, Joshua Ulrich points out in the comments that "Dates don't have timezones because days don't have a time component."
On the third problem: The data frame appears to have corrupted dates, as pointed out by agstudy in the comments.
My solutions rely on the quantmod package and the attached zoo/xts packages:
library(quantmod)
Here is the code I have used to get proper dates from my csv file:
toDate <- function(x){ as.Date(as.character(x), format("%Y%m%d")) }
dtz <- read.zoo("myData.csv"
, header = TRUE
, sep = ","
, FUN = toDate
)
dtx <- as.xts(dtz)
The dates in the csv file were stored in a single column in the format "19861231". The key to getting correct dates was to wrap the date in "as.character()". Part of this code was inspired from R - Stock market data from csv to xts. I also found the zoo/xts manuals helpful.
I then extract the date range from this dataset:
date.start <- start(dtx)
date.end <- end(dtx)
I will use those dates with quantmod's getSymbols function so that the other data I download will cover the same period.
Here is the code I have used to get all three tickers.
tickers <- c("^GSPC","^TYX","DJIA")
data <- new.env() # the data environment will store the data
do.call(cbind, lapply( tickers
, getSymbols
, from = date.start
, to = date.end
, env = data # data saved inside an environment
)
)
ls(data) # see what's inside the data environment
data$GSPC # access a particular ticker
Also note, as GSee pointed out in the comments, that the option auto.assign=FALSE cannot be used in conjunction with the option env=data (otherwise the download fails).
A big thank you for your help.
Yahoo doesn't provide historical data for ^DJI. Currently, it looks like you can get the same data by using the ticker "DJIA", but your mileage may vary.
It does work in this case because you're only dealing with Dates
the df object your provided is yearly data beginning in the year 0001. So, that's probably not what you wanted.
Here's how I would fetch and merge those series (or use an environment and only make one call to getSymbols)
library(quantmod)
do.call(cbind, lapply(c("^GSPC", "^TYX"), getSymbols, auto.assign=FALSE))

Resources