Welcome to the second episode of our R from Zero to Hero journey. In
this section we will look at how to get data into our “screen”, in front
of our eyes. In other words, how to load or import data, but also how to
save data after some process has been completed.
Data collection
There are various way to get data to our screens, depending on the
origin or source of data. We will try to cover these methods
briefly.
External data sources
we called them external because they are not currently located in our
environment - not from our station necessary.
Loading data from a folder
In the early days of your R journey, you are most likely to load data
from MyDocument folder. We will look at various case here, depending on
the format of the file.
Loadind data from an Excel file
library(readxl)
my_dataframe_xlsx <- read_xlsx('./data/insider-purchases.xlsx')
print(head(my_dataframe_xlsx))
Loadind data from a CSV file
my_dataframe_csv <- read.csv('./data/insider-purchases.csv')
print(head(my_dataframe_csv))
NA
Loadind data from a PDF file
Depending on the industry or the subject that we are working on, we
might have to import data from a PDF file. While this is not usually
demonstrated in entry level material, I choose to cover it here, because
you will not have the choice on the format of data that are made
available to you.
library(tabulapdf)
#path_to_file <- "data/insider-purchases-pdf.pdf"
#my_dataframe_pdf <- extract_tables(path_to_file)
#### WE WILL HAVE TO COME BACK HERE TO HANDLE MULTI PAGES PDF TABLE
Loadind data from RDS file
The RDS format in R is a file format used to store a single R object,
created using the saveRDS() function and read using the
readRDS() function. The primary advantages of RDS are:
- Serialization of Single Objects: RDS files store a
single R object, making it simple to save and load specific data
structures, models, or any R objects.
- Preservation of Object Structure: RDS preserves the
exact structure and attributes of the saved R object, ensuring
consistency when reloaded.
- Compact Storage: RDS files are often smaller due to
internal compression, which saves disk space.
- Flexibility in Naming: Unlike other formats, when
reading an RDS file, you can assign any name to the loaded object.
- Interoperability: RDS files can be easily shared
and used across different R sessions and environments, enhancing
reproducibility.
my_dataframe_rds <- readRDS('data/my_dataframe_rds.rds')
print(head(my_dataframe_rds))
NA
Loading data from a database
# here develop how to use Postgres etc
Data Export
After completing certain task on our data, we would most likely need
to keep some of the records,…or all of them. For this purpose we will do
the exact opposite of the data collection processes: we will export our
data. In this section we will use the opposite of the ‘loading’
functions that were previously used. Before exporting our data, we will
print a preview, just for sanity checks. we will use the dataset that
was previously loaded from an RDS file - this is a discretionary
choice.
dataset_to_be_exported <- my_dataframe_rds
print(head(dataset_to_be_exported))
NA
Exporting to Excel file
xlsx::write.xlsx(dataset_to_be_exported, 'data/exports/dataset_to_be_exported.xlsx')
Because this will likely happen, you might want to save this file
using the date of today as a key differentiator, we better as well learn
how to do it now. We will rely on the paste function of R, as well ad
the date formating funtcion - note we can apply that to any file naming
prior to saving.
Exporting to an Excel file with a date inside the name
# Format the date of today
today_date_formated = format(as.Date(Sys.Date()), format="%Y-%B-%d")
#Create the file name string
file_name = paste(today_date_formated,"dataset_to_be_exported.xlsx", sep=" ")
# Get the path to export
file_path_for_export = paste(getwd(),'/data/exports/', file_name, sep='')
# Export the file to the previously created name
xlsx::write.xlsx(dataset_to_be_exported,file_path_for_export)
Ah, what just happened. We just used two functions that we have never
seen before. 1 - Sys.Date(): This method gives us the current date of
our machine. We could also use Sys.time(), which would return for
example: “2020-05-31 14:49:47 HKT” 2 - getwd()….
Exporting to CSV file
write.csv(dataset_to_be_exported, 'data/exports/dataset_to_be_exported.csv')
Exporting to RDS file
saveRDS(dataset_to_be_exported, 'data/exports/dataset_to_be_exported.rds')
Exporting to Database
We will leave the following section for now, we will get back at it
later with the actual setup of a database - we need to consider what is
the best option for data exchange here
Database set up and connection
The folliwing section assumes that you have already setup a database
once again.
library(RPostgres)
con <-
dbConnect(
RPostgres::Postgres(),
dbname = "market_data",
host = "localhost",
port = 5432,
user = "myself",
password = "123456",
)
Updating Database record - with append
dbWriteTable(
con2,
"Example1",
dataset_to_be_exported,
overwrite = FALSE,
row.names = FALSE
)
Updating Database record - with replace
dbWriteTable(
con2,
"Example1",
dataset_to_be_exported,
overwrite = TRUE,
row.names = FALSE
)
Internal data sources - saving and loading an environment
With internal data source, we include anything that is in our current
environment. Assume that we have done some work on multiple dataframe,
and we are not looking to individually save each of them in an excel,
CSV or RDS file, because there would be 100 of them. Well R allows us to
save the entire environment, as it is, with all the variables,
dataframe, etc. that we have been working on. Let’s have a look at the
command for that purpose.
Saving an environment
save.image(file='myEnvironment.RData')
Loading an environment
load('myHeroEnvironment.RData')
This is it for importing and exporting data for today, we will now
move to Data Exploration, which is a critical step in our Machine
Learning Pipeline
LS0tDQp0aXRsZTogIlIsIGZyb20gWmVybyB0byBIZXJvOiBEYXRhIE1hbmFnZW1lbnQgKEltcG9ydGluZywgbG9hZGluZywgc2F2aW5nKSINCmF1dGhvcjogIkZyYW50eiBNb3Vkb3V0ZSINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCldlbGNvbWUgdG8gdGhlIHNlY29uZCBlcGlzb2RlIG9mIG91ciBSIGZyb20gWmVybyB0byBIZXJvIGpvdXJuZXkuIEluIHRoaXMgc2VjdGlvbiB3ZSB3aWxsIGxvb2sgYXQgaG93IHRvIGdldCBkYXRhIGludG8gb3VyICJzY3JlZW4iLCBpbiBmcm9udCBvZiBvdXIgZXllcy4gSW4gb3RoZXIgd29yZHMsIGhvdyB0byBsb2FkIG9yIGltcG9ydCBkYXRhLCBidXQgYWxzbyBob3cgdG8gc2F2ZSBkYXRhIGFmdGVyIHNvbWUgcHJvY2VzcyBoYXMgYmVlbiBjb21wbGV0ZWQuDQoNCiMgRGF0YSBjb2xsZWN0aW9uDQpUaGVyZSBhcmUgdmFyaW91cyB3YXkgdG8gZ2V0IGRhdGEgdG8gb3VyIHNjcmVlbnMsIGRlcGVuZGluZyBvbiB0aGUgb3JpZ2luIG9yIHNvdXJjZSBvZiBkYXRhLiBXZSB3aWxsIHRyeSB0byBjb3ZlciB0aGVzZSBtZXRob2RzIGJyaWVmbHkuDQoNCiMjIEV4dGVybmFsIGRhdGEgc291cmNlcw0Kd2UgY2FsbGVkIHRoZW0gZXh0ZXJuYWwgYmVjYXVzZSB0aGV5IGFyZSBub3QgY3VycmVudGx5IGxvY2F0ZWQgaW4gb3VyIGVudmlyb25tZW50IC0gbm90IGZyb20gb3VyIHN0YXRpb24gbmVjZXNzYXJ5LiANCg0KIyMjIExvYWRpbmcgZGF0YSBmcm9tIGEgZm9sZGVyDQpJbiB0aGUgZWFybHkgZGF5cyBvZiB5b3VyIFIgam91cm5leSwgeW91IGFyZSBtb3N0IGxpa2VseSB0byBsb2FkIGRhdGEgZnJvbSBNeURvY3VtZW50IGZvbGRlci4gV2Ugd2lsbCBsb29rIGF0IHZhcmlvdXMgY2FzZSBoZXJlLCBkZXBlbmRpbmcgb24gdGhlIGZvcm1hdCBvZiB0aGUgZmlsZS4NCg0KIyMjIyBMb2FkaW5kIGRhdGEgZnJvbSBhbiBFeGNlbCBmaWxlDQpgYGB7cn0NCmxpYnJhcnkocmVhZHhsKQ0KbXlfZGF0YWZyYW1lX3hsc3ggPC0gcmVhZF94bHN4KCcuL2RhdGEvaW5zaWRlci1wdXJjaGFzZXMueGxzeCcpDQpwcmludChoZWFkKG15X2RhdGFmcmFtZV94bHN4KSkNCmBgYA0KDQoNCiMjIyMgTG9hZGluZCBkYXRhIGZyb20gYSBDU1YgZmlsZQ0KYGBge3J9DQpteV9kYXRhZnJhbWVfY3N2IDwtIHJlYWQuY3N2KCcuL2RhdGEvaW5zaWRlci1wdXJjaGFzZXMuY3N2JykNCnByaW50KGhlYWQobXlfZGF0YWZyYW1lX2NzdikpDQoNCmBgYA0KDQoNCiMjIyMgTG9hZGluZCBkYXRhIGZyb20gYSBQREYgZmlsZQ0KRGVwZW5kaW5nIG9uIHRoZSBpbmR1c3RyeSBvciB0aGUgc3ViamVjdCB0aGF0IHdlIGFyZSB3b3JraW5nIG9uLCB3ZSBtaWdodCBoYXZlIHRvIGltcG9ydCBkYXRhIGZyb20gYSBQREYgZmlsZS4gV2hpbGUgdGhpcyBpcyBub3QgdXN1YWxseSBkZW1vbnN0cmF0ZWQgaW4gZW50cnkgbGV2ZWwgbWF0ZXJpYWwsIEkgY2hvb3NlIHRvIGNvdmVyIGl0IGhlcmUsIGJlY2F1c2UgeW91IHdpbGwgbm90IGhhdmUgdGhlIGNob2ljZSBvbiB0aGUgZm9ybWF0IG9mIGRhdGEgdGhhdCBhcmUgbWFkZSBhdmFpbGFibGUgdG8geW91Lg0KYGBge3J9DQpsaWJyYXJ5KHRhYnVsYXBkZikNCiNwYXRoX3RvX2ZpbGUgPC0gImRhdGEvaW5zaWRlci1wdXJjaGFzZXMtcGRmLnBkZiINCiNteV9kYXRhZnJhbWVfcGRmIDwtIGV4dHJhY3RfdGFibGVzKHBhdGhfdG9fZmlsZSkNCiMjIyMgV0UgV0lMTCBIQVZFIFRPIENPTUUgQkFDSyBIRVJFIFRPIEhBTkRMRSBNVUxUSSBQQUdFUyBQREYgVEFCTEUNCmBgYA0KDQoNCg0KIyMjIyBMb2FkaW5kIGRhdGEgZnJvbSBSRFMgZmlsZQ0KVGhlIFJEUyBmb3JtYXQgaW4gUiBpcyBhIGZpbGUgZm9ybWF0IHVzZWQgdG8gc3RvcmUgYSBzaW5nbGUgUiBvYmplY3QsIGNyZWF0ZWQgdXNpbmcgdGhlIGBzYXZlUkRTKClgIGZ1bmN0aW9uIGFuZCByZWFkIHVzaW5nIHRoZSBgcmVhZFJEUygpYCBmdW5jdGlvbi4gVGhlIHByaW1hcnkgYWR2YW50YWdlcyBvZiBSRFMgYXJlOg0KDQoxLiAqKlNlcmlhbGl6YXRpb24gb2YgU2luZ2xlIE9iamVjdHMqKjogUkRTIGZpbGVzIHN0b3JlIGEgc2luZ2xlIFIgb2JqZWN0LCBtYWtpbmcgaXQgc2ltcGxlIHRvIHNhdmUgYW5kIGxvYWQgc3BlY2lmaWMgZGF0YSBzdHJ1Y3R1cmVzLCBtb2RlbHMsIG9yIGFueSBSIG9iamVjdHMuDQoyLiAqKlByZXNlcnZhdGlvbiBvZiBPYmplY3QgU3RydWN0dXJlKio6IFJEUyBwcmVzZXJ2ZXMgdGhlIGV4YWN0IHN0cnVjdHVyZSBhbmQgYXR0cmlidXRlcyBvZiB0aGUgc2F2ZWQgUiBvYmplY3QsIGVuc3VyaW5nIGNvbnNpc3RlbmN5IHdoZW4gcmVsb2FkZWQuDQozLiAqKkNvbXBhY3QgU3RvcmFnZSoqOiBSRFMgZmlsZXMgYXJlIG9mdGVuIHNtYWxsZXIgZHVlIHRvIGludGVybmFsIGNvbXByZXNzaW9uLCB3aGljaCBzYXZlcyBkaXNrIHNwYWNlLg0KNC4gKipGbGV4aWJpbGl0eSBpbiBOYW1pbmcqKjogVW5saWtlIG90aGVyIGZvcm1hdHMsIHdoZW4gcmVhZGluZyBhbiBSRFMgZmlsZSwgeW91IGNhbiBhc3NpZ24gYW55IG5hbWUgdG8gdGhlIGxvYWRlZCBvYmplY3QuDQo1LiAqKkludGVyb3BlcmFiaWxpdHkqKjogUkRTIGZpbGVzIGNhbiBiZSBlYXNpbHkgc2hhcmVkIGFuZCB1c2VkIGFjcm9zcyBkaWZmZXJlbnQgUiBzZXNzaW9ucyBhbmQgZW52aXJvbm1lbnRzLCBlbmhhbmNpbmcgcmVwcm9kdWNpYmlsaXR5Lg0KDQoNCmBgYHtyfQ0KbXlfZGF0YWZyYW1lX3JkcyA8LSByZWFkUkRTKCdkYXRhL215X2RhdGFmcmFtZV9yZHMucmRzJykNCnByaW50KGhlYWQobXlfZGF0YWZyYW1lX3JkcykpDQoNCmBgYA0KIyMjIExvYWRpbmcgZGF0YSBmcm9tIGEgZGF0YWJhc2UNCg0KYGBge3J9DQojIGhlcmUgZGV2ZWxvcCBob3cgdG8gdXNlIFBvc3RncmVzIGV0Yw0KYGBgDQoNCiMgRGF0YSBFeHBvcnQNCkFmdGVyIGNvbXBsZXRpbmcgY2VydGFpbiB0YXNrIG9uIG91ciBkYXRhLCB3ZSB3b3VsZCBtb3N0IGxpa2VseSBuZWVkIHRvIGtlZXAgc29tZSBvZiB0aGUgcmVjb3JkcywuLi5vciBhbGwgb2YgdGhlbS4gRm9yIHRoaXMgcHVycG9zZSB3ZSB3aWxsIGRvIHRoZSBleGFjdCBvcHBvc2l0ZSBvZiB0aGUgZGF0YSBjb2xsZWN0aW9uIHByb2Nlc3Nlczogd2Ugd2lsbCBleHBvcnQgb3VyIGRhdGEuIEluIHRoaXMgc2VjdGlvbiB3ZSB3aWxsIHVzZSB0aGUgb3Bwb3NpdGUgb2YgdGhlICdsb2FkaW5nJyBmdW5jdGlvbnMgdGhhdCB3ZXJlIHByZXZpb3VzbHkgdXNlZC4gDQpCZWZvcmUgZXhwb3J0aW5nIG91ciBkYXRhLCB3ZSB3aWxsIHByaW50IGEgcHJldmlldywganVzdCBmb3Igc2FuaXR5IGNoZWNrcy4gd2Ugd2lsbCB1c2UgdGhlIGRhdGFzZXQgdGhhdCB3YXMgcHJldmlvdXNseSBsb2FkZWQgZnJvbSBhbiBSRFMgZmlsZSAtIHRoaXMgaXMgYSBkaXNjcmV0aW9uYXJ5IGNob2ljZS4NCg0KYGBge3J9DQpkYXRhc2V0X3RvX2JlX2V4cG9ydGVkIDwtIG15X2RhdGFmcmFtZV9yZHMNCg0KcHJpbnQoaGVhZChkYXRhc2V0X3RvX2JlX2V4cG9ydGVkKSkNCg0KYGBgDQoNCg0KIyMgRXhwb3J0aW5nIHRvIEV4Y2VsIGZpbGUNCmBgYHtyfQ0KeGxzeDo6d3JpdGUueGxzeChkYXRhc2V0X3RvX2JlX2V4cG9ydGVkLCAnZGF0YS9leHBvcnRzL2RhdGFzZXRfdG9fYmVfZXhwb3J0ZWQueGxzeCcpDQoNCmBgYA0KQmVjYXVzZSB0aGlzIHdpbGwgbGlrZWx5IGhhcHBlbiwgIHlvdSBtaWdodCB3YW50IHRvIHNhdmUgdGhpcyBmaWxlIHVzaW5nIHRoZSBkYXRlIG9mIHRvZGF5IGFzIGEga2V5IGRpZmZlcmVudGlhdG9yLCB3ZSBiZXR0ZXIgYXMgd2VsbCBsZWFybiBob3cgdG8gZG8gaXQgbm93LiBXZSB3aWxsIHJlbHkgb24gdGhlIHBhc3RlIGZ1bmN0aW9uIG9mIFIsIGFzIHdlbGwgYWQgdGhlIGRhdGUgZm9ybWF0aW5nIGZ1bnRjaW9uIC0gbm90ZSB3ZSBjYW4gYXBwbHkgdGhhdCB0byBhbnkgZmlsZSBuYW1pbmcgcHJpb3IgdG8gc2F2aW5nLiANCg0KIyMjIEV4cG9ydGluZyB0byBhbiBFeGNlbCBmaWxlIHdpdGggYSBkYXRlIGluc2lkZSB0aGUgbmFtZQ0KYGBge3J9DQoNCiMgRm9ybWF0IHRoZSBkYXRlIG9mIHRvZGF5DQp0b2RheV9kYXRlX2Zvcm1hdGVkID0gZm9ybWF0KGFzLkRhdGUoU3lzLkRhdGUoKSksIGZvcm1hdD0iJVktJUItJWQiKQ0KDQojQ3JlYXRlIHRoZSBmaWxlIG5hbWUgc3RyaW5nDQpmaWxlX25hbWUgPSBwYXN0ZSh0b2RheV9kYXRlX2Zvcm1hdGVkLCJkYXRhc2V0X3RvX2JlX2V4cG9ydGVkLnhsc3giLCBzZXA9IiAiKQ0KDQojIEdldCB0aGUgcGF0aCB0byBleHBvcnQNCmZpbGVfcGF0aF9mb3JfZXhwb3J0ID0gcGFzdGUoZ2V0d2QoKSwnL2RhdGEvZXhwb3J0cy8nLCBmaWxlX25hbWUsIHNlcD0nJykNCg0KIyBFeHBvcnQgdGhlIGZpbGUgdG8gdGhlIHByZXZpb3VzbHkgY3JlYXRlZCBuYW1lDQp4bHN4Ojp3cml0ZS54bHN4KGRhdGFzZXRfdG9fYmVfZXhwb3J0ZWQsZmlsZV9wYXRoX2Zvcl9leHBvcnQpDQpgYGANCg0KQWgsIHdoYXQganVzdCBoYXBwZW5lZC4gV2UganVzdCB1c2VkIHR3byBmdW5jdGlvbnMgdGhhdCB3ZSBoYXZlIG5ldmVyIHNlZW4gYmVmb3JlLiANCjEgLSBTeXMuRGF0ZSgpOiBUaGlzIG1ldGhvZCBnaXZlcyB1cyB0aGUgY3VycmVudCBkYXRlIG9mIG91ciBtYWNoaW5lLiBXZSBjb3VsZCBhbHNvIHVzZSBTeXMudGltZSgpLCB3aGljaCB3b3VsZCByZXR1cm4gZm9yIGV4YW1wbGU6ICIyMDIwLTA1LTMxIDE0OjQ5OjQ3IEhLVCINCjIgLSBnZXR3ZCgpLi4uLg0KDQoNCg0KDQoNCg0KIyMgRXhwb3J0aW5nIHRvIENTViBmaWxlDQpgYGB7cn0NCndyaXRlLmNzdihkYXRhc2V0X3RvX2JlX2V4cG9ydGVkLCAnZGF0YS9leHBvcnRzL2RhdGFzZXRfdG9fYmVfZXhwb3J0ZWQuY3N2JykNCmBgYA0KDQoNCiMjIEV4cG9ydGluZyB0byBSRFMgZmlsZQ0KYGBge3J9DQpzYXZlUkRTKGRhdGFzZXRfdG9fYmVfZXhwb3J0ZWQsICdkYXRhL2V4cG9ydHMvZGF0YXNldF90b19iZV9leHBvcnRlZC5yZHMnKQ0KYGBgDQoNCg0KDQojIyBFeHBvcnRpbmcgdG8gRGF0YWJhc2UNCldlIHdpbGwgbGVhdmUgdGhlIGZvbGxvd2luZyBzZWN0aW9uIGZvciBub3csIHdlIHdpbGwgZ2V0IGJhY2sgYXQgaXQgbGF0ZXIgd2l0aCB0aGUgYWN0dWFsIHNldHVwIG9mIGEgZGF0YWJhc2UgIC0gd2UgbmVlZCB0byBjb25zaWRlciB3aGF0IGlzIHRoZSBiZXN0IG9wdGlvbiBmb3IgZGF0YSBleGNoYW5nZSBoZXJlDQoNCiMjIyBEYXRhYmFzZSBzZXQgdXAgYW5kIGNvbm5lY3Rpb24NClRoZSBmb2xsaXdpbmcgc2VjdGlvbiBhc3N1bWVzIHRoYXQgeW91IGhhdmUgYWxyZWFkeSBzZXR1cCBhIGRhdGFiYXNlIG9uY2UgYWdhaW4uIA0KYGBge3J9DQpsaWJyYXJ5KFJQb3N0Z3JlcykNCmNvbiA8LQ0KICBkYkNvbm5lY3QoDQogICAgUlBvc3RncmVzOjpQb3N0Z3JlcygpLA0KICAgIGRibmFtZSA9ICJtYXJrZXRfZGF0YSIsDQogICAgaG9zdCA9ICJsb2NhbGhvc3QiLA0KICAgIHBvcnQgPSA1NDMyLA0KICAgIHVzZXIgPSAibXlzZWxmIiwNCiAgICBwYXNzd29yZCA9ICIxMjM0NTYiLA0KICApDQpgYGANCg0KIyMjIFVwZGF0aW5nIERhdGFiYXNlIHJlY29yZCAtIHdpdGggYXBwZW5kDQpgYGB7cn0NCmRiV3JpdGVUYWJsZSgNCiAgY29uMiwNCiAgIkV4YW1wbGUxIiwNCiAgZGF0YXNldF90b19iZV9leHBvcnRlZCwNCiAgb3ZlcndyaXRlID0gRkFMU0UsDQogIHJvdy5uYW1lcyA9IEZBTFNFDQopDQpgYGANCg0KDQojIyMgVXBkYXRpbmcgRGF0YWJhc2UgcmVjb3JkIC0gd2l0aCByZXBsYWNlDQpgYGB7cn0NCmRiV3JpdGVUYWJsZSgNCiAgY29uMiwNCiAgIkV4YW1wbGUxIiwNCiAgZGF0YXNldF90b19iZV9leHBvcnRlZCwNCiAgb3ZlcndyaXRlID0gVFJVRSwNCiAgcm93Lm5hbWVzID0gRkFMU0UNCikNCmBgYA0KDQoNCiMjIEludGVybmFsIGRhdGEgc291cmNlcyAtIHNhdmluZyBhbmQgbG9hZGluZyBhbiBlbnZpcm9ubWVudA0KV2l0aCBpbnRlcm5hbCBkYXRhIHNvdXJjZSwgd2UgaW5jbHVkZSBhbnl0aGluZyB0aGF0IGlzIGluIG91ciBjdXJyZW50IGVudmlyb25tZW50LiBBc3N1bWUgdGhhdCB3ZSBoYXZlIGRvbmUgc29tZSB3b3JrIG9uIG11bHRpcGxlIGRhdGFmcmFtZSwgYW5kIHdlIGFyZSBub3QgbG9va2luZyB0byBpbmRpdmlkdWFsbHkgc2F2ZSBlYWNoIG9mIHRoZW0gaW4gYW4gZXhjZWwsIENTViBvciBSRFMgZmlsZSwgYmVjYXVzZSB0aGVyZSB3b3VsZCBiZSAxMDAgb2YgdGhlbS4gV2VsbCBSIGFsbG93cyB1cyB0byBzYXZlIHRoZSBlbnRpcmUgZW52aXJvbm1lbnQsIGFzIGl0IGlzLCB3aXRoIGFsbCB0aGUgdmFyaWFibGVzLCBkYXRhZnJhbWUsIGV0Yy4gdGhhdCB3ZSBoYXZlIGJlZW4gd29ya2luZyBvbi4gTGV0J3MgaGF2ZSBhIGxvb2sgYXQgdGhlIGNvbW1hbmQgZm9yIHRoYXQgcHVycG9zZS4NCg0KIyMjIFNhdmluZyBhbiBlbnZpcm9ubWVudA0KYGBge3J9DQpzYXZlLmltYWdlKGZpbGU9J215SGVyb0Vudmlyb25tZW50LlJEYXRhJykNCmBgYA0KDQoNCiMjIyBMb2FkaW5nIGFuIGVudmlyb25tZW50DQpgYGB7cn0NCgkNCmxvYWQoJ215SGVyb0Vudmlyb25tZW50LlJEYXRhJykNCmBgYA0KVGhpcyBpcyBpdCBmb3IgaW1wb3J0aW5nIGFuZCBleHBvcnRpbmcgZGF0YSBmb3IgdG9kYXksIHdlIHdpbGwgbm93IG1vdmUgdG8gRGF0YSBFeHBsb3JhdGlvbiwgd2hpY2ggaXMgYSBjcml0aWNhbCBzdGVwIGluIG91ciBNYWNoaW5lIExlYXJuaW5nIFBpcGVsaW5lDQoNCg0KDQo=