This lesson introduces some ways to deal with missing values. It’s important that missing values are either removed or filled in with imputed values so that algorithms do not throw errors.

Preliminaries

Load the dplyr and magrittr packages.

library(dplyr)
library(magrittr)

Make sure that this file and the jan17Items.csv file are in the same folder and that the working directory is set to that folder.

Read in the jan17Items data as j17i.

j17i <- read.csv('jan17Items.csv')

Missing Values Review

NA and NaN values are treated differently than numeric values. For instance, when using the sum() function, you have to explicitly indicate that you want to remove NAs from the computation or else it will cause an error.

v1 <- c(1,NA,3,9,15,NA)
sum(v1, na.rm = T)
[1] 28

Testing if a value is equal to NA is different from testing if a value is equal to a numeric or string value. You must use is.na() rather than ==.

4 == NA # Returns NA
[1] NA
is.na(4) # Returns FALSE. 
[1] FALSE

Identifying the Pattern of Missing Values

When exploring a new dataset, it’s worthwhile to identify the pattern of missing values. The summary() includes the number of missing values for each column along with the summary statistics.

summary(j17i)
     Time           OperationType        BarCode          CashierName       
 Length:8899        Length:8899        Length:8899        Length:8899       
 Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                            
                                                                            
                                                                            
                                                                            
   LineItem          Department          Category         CardholderName    
 Length:8899        Length:8899        Length:8899        Length:8899       
 Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                            
                                                                            
                                                                            
                                                                            
 RegisterName       StoreNumber        TransactionNumber  CustomerCode      
 Length:8899        Length:8899        Length:8899        Length:8899       
 Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                            
                                                                            
                                                                            
                                                                            
      Cost               Price             Quantity        Modifiers      
 Min.   :-189.0400   Min.   :-322.810   Min.   : 1.000   Min.   :-2.5200  
 1st Qu.:   0.1100   1st Qu.:   4.500   1st Qu.: 1.000   1st Qu.: 0.0100  
 Median :   0.1100   Median :  12.020   Median : 1.000   Median : 0.0100  
 Mean   :   0.2108   Mean   :   9.612   Mean   : 1.177   Mean   : 0.6548  
 3rd Qu.:   0.1100   3rd Qu.:  14.680   3rd Qu.: 1.000   3rd Qu.: 1.0800  
 Max.   : 189.0300   Max.   :  24.630   Max.   :36.000   Max.   :57.5500  
                     NA's   :562                                          
    Subtotal         Discounts           NetTotal            Tax         
 Min.   :-322.80   Min.   : -0.0300   Min.   :-322.77   Min.   :-25.340  
 1st Qu.:   5.24   1st Qu.: -0.0300   1st Qu.:   4.87   1st Qu.:  0.410  
 Median :  13.10   Median : -0.0300   Median :  13.13   Median :  1.040  
 Mean   :  15.10   Mean   :  0.7626   Mean   :  14.34   Mean   :  1.131  
 3rd Qu.:  15.76   3rd Qu.: -0.0300   3rd Qu.:  15.79   3rd Qu.:  1.230  
 Max.   :3328.12   Max.   :951.8300   Max.   :3328.15   Max.   :261.260  
                                                        NA's   :341      
    TotalDue      
 Min.   :-348.11  
 1st Qu.:   5.68  
 Median :  14.17  
 Mean   :  15.88  
 3rd Qu.:  17.02  
 Max.   :3589.41  
 NA's   :341      

Notice that the Price, Tax, and TotalDue columns have missing values. The others numeric columns do not. Interesting that the number of missing values for Tax and TotalDue are the same. A visual inspection confirms that these are associated with the same observations.

Removing Missing Values

If a column has a large percentage of missing values, then you may want to consider dropping that column all together. However, if you are really interested in the effect of that column, then a better idea is to remove the observations that have missing values. To indicate that you want to keep observations for which the value is NOT NA, then use the exclamation point to negate the is.na() function.

j17i_2 <- j17i %>%
  filter(!is.na(Price))
summary(j17i_2)
     Time           OperationType        BarCode          CashierName       
 Length:8337        Length:8337        Length:8337        Length:8337       
 Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                            
                                                                            
                                                                            
                                                                            
   LineItem          Department          Category         CardholderName    
 Length:8337        Length:8337        Length:8337        Length:8337       
 Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                            
                                                                            
                                                                            
                                                                            
 RegisterName       StoreNumber        TransactionNumber  CustomerCode      
 Length:8337        Length:8337        Length:8337        Length:8337       
 Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                            
                                                                            
                                                                            
                                                                            
      Cost                Price             Quantity        Modifiers      
 Min.   :-189.04000   Min.   :-322.810   Min.   : 1.000   Min.   :-2.5200  
 1st Qu.:   0.11000   1st Qu.:   4.500   1st Qu.: 1.000   1st Qu.: 0.0100  
 Median :   0.11000   Median :  12.020   Median : 1.000   Median : 0.0100  
 Mean   :   0.04306   Mean   :   9.612   Mean   : 1.186   Mean   : 0.6982  
 3rd Qu.:   0.11000   3rd Qu.:  14.680   3rd Qu.: 1.000   3rd Qu.: 1.0800  
 Max.   :   7.64000   Max.   :  24.630   Max.   :36.000   Max.   :57.5500  
                                                                           
    Subtotal         Discounts           NetTotal            Tax          
 Min.   :-322.80   Min.   : -0.0300   Min.   :-322.77   Min.   :-25.3400  
 1st Qu.:   4.84   1st Qu.: -0.0300   1st Qu.:   4.54   1st Qu.:  0.4000  
 Median :  12.61   Median : -0.0300   Median :  12.06   Median :  1.0000  
 Mean   :  11.16   Mean   :  0.3395   Mean   :  10.82   Mean   :  0.8769  
 3rd Qu.:  15.64   3rd Qu.: -0.0300   3rd Qu.:  15.50   3rd Qu.:  1.2200  
 Max.   : 496.99   Max.   :396.1100   Max.   : 212.35   Max.   : 16.6100  
                                                        NA's   :328       
    TotalDue      
 Min.   :-348.11  
 1st Qu.:   5.50  
 Median :  13.70  
 Mean   :  12.07  
 3rd Qu.:  16.89  
 Max.   : 228.96  
 NA's   :328      

Notice that by removing the observations with missing values for Price must have included some observations for which Tax and TotalDue were missing because there are only 328 missing values instead of 341.

I would give a quick visual inspection to the observations that are missing values for Tax to see if I can identify a pattern. If the pattern isn’t meaningful, then I would export the transactions and go over them with the cashier or manager.

missingPrice <- j17i %>%
  filter(is.na(Price))

There are also functions in other packages that help you visualize the pattern of missing values to help you get an idea of the pattern of missing values.

Imputing Missing Values with the ifelse Function

If you don’t want to lose the other information from the incomplete observation, then you can fill in the missing values with the mean or median value from that column. To do that, you can use the ifelse() function, which is identical to the =IF() function in Excel. We’ll do that within the mutate function, and then calculate the value for TotalDue.

?ifelse
j17i_3 <- j17i %>%
  mutate(
    Tax = ifelse(is.na(Tax), mean(Tax, na.rm = T), Tax) # Could use median() instead of mean() if there are outliers
    , TotalDue = NetTotal + Tax
  )
summary(j17i_3)
     Time           OperationType        BarCode          CashierName       
 Length:8899        Length:8899        Length:8899        Length:8899       
 Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                            
                                                                            
                                                                            
                                                                            
   LineItem          Department          Category         CardholderName    
 Length:8899        Length:8899        Length:8899        Length:8899       
 Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                            
                                                                            
                                                                            
                                                                            
 RegisterName       StoreNumber        TransactionNumber  CustomerCode      
 Length:8899        Length:8899        Length:8899        Length:8899       
 Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                            
                                                                            
                                                                            
                                                                            
      Cost               Price             Quantity        Modifiers      
 Min.   :-189.0400   Min.   :-322.810   Min.   : 1.000   Min.   :-2.5200  
 1st Qu.:   0.1100   1st Qu.:   4.500   1st Qu.: 1.000   1st Qu.: 0.0100  
 Median :   0.1100   Median :  12.020   Median : 1.000   Median : 0.0100  
 Mean   :   0.2108   Mean   :   9.612   Mean   : 1.177   Mean   : 0.6548  
 3rd Qu.:   0.1100   3rd Qu.:  14.680   3rd Qu.: 1.000   3rd Qu.: 1.0800  
 Max.   : 189.0300   Max.   :  24.630   Max.   :36.000   Max.   :57.5500  
                     NA's   :562                                          
    Subtotal         Discounts           NetTotal            Tax         
 Min.   :-322.80   Min.   : -0.0300   Min.   :-322.77   Min.   :-25.340  
 1st Qu.:   5.24   1st Qu.: -0.0300   1st Qu.:   4.87   1st Qu.:  0.460  
 Median :  13.10   Median : -0.0300   Median :  13.13   Median :  1.040  
 Mean   :  15.10   Mean   :  0.7626   Mean   :  14.34   Mean   :  1.131  
 3rd Qu.:  15.76   3rd Qu.: -0.0300   3rd Qu.:  15.79   3rd Qu.:  1.230  
 Max.   :3328.12   Max.   :951.8300   Max.   :3328.15   Max.   :261.260  
                                                                         
    TotalDue      
 Min.   :-348.11  
 1st Qu.:   5.25  
 Median :  14.17  
 Mean   :  15.47  
 3rd Qu.:  17.02  
 Max.   :3589.41  
                  

The ifelse function is powerful, and you can use it in many other ways. Conditional statements will be covered elsewhere in more depth.

There are other ways for dealing with missing values. A model-based approach fills in missing values based on values from one or more other columns.

Concluding Comments

You can see that the easiest approach for dealing with missing values is to remove the columns or the observations; however, you may be throwing the baby out with the bathwater, especially with small datasets. Ultimately, you’ll have to use judgment for dealing with missing values. The important thing is to fully disclose what you do.

The best approach is to perform your analyses using both approaches and then identify if the results change based on what you do. If the results of your do not qualitatively change, then it’s not a big deal. If the results are different, then think carefully about which approach is best and justify the approach that you think is best.

LS0tDQp0aXRsZTogIkhhbmRsaW5nIE1pc3NpbmcgVmFsdWVzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NClRoaXMgbGVzc29uIGludHJvZHVjZXMgc29tZSB3YXlzIHRvIGRlYWwgd2l0aCBtaXNzaW5nIHZhbHVlcy4gSXQncyBpbXBvcnRhbnQgdGhhdCBtaXNzaW5nIHZhbHVlcyBhcmUgZWl0aGVyIHJlbW92ZWQgb3IgZmlsbGVkIGluIHdpdGggaW1wdXRlZCB2YWx1ZXMgc28gdGhhdCBhbGdvcml0aG1zIGRvIG5vdCB0aHJvdyBlcnJvcnMuDQoNCiMjIFByZWxpbWluYXJpZXMNCkxvYWQgdGhlIGRwbHlyIGFuZCBtYWdyaXR0ciBwYWNrYWdlcy4NCmBgYHtyfQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkobWFncml0dHIpDQpgYGANCk1ha2Ugc3VyZSB0aGF0IHRoaXMgZmlsZSBhbmQgdGhlIGphbjE3SXRlbXMuY3N2IGZpbGUgYXJlIGluIHRoZSBzYW1lIGZvbGRlciBhbmQgdGhhdCB0aGUgd29ya2luZyBkaXJlY3RvcnkgaXMgc2V0IHRvIHRoYXQgZm9sZGVyLg0KDQpSZWFkIGluIHRoZSBqYW4xN0l0ZW1zIGRhdGEgYXMgajE3aS4NCmBgYHtyfQ0KajE3aSA8LSByZWFkLmNzdignamFuMTdJdGVtcy5jc3YnKQ0KYGBgDQoNCiMjIE1pc3NpbmcgVmFsdWVzIFJldmlldw0KTkEgYW5kIE5hTiB2YWx1ZXMgYXJlIHRyZWF0ZWQgZGlmZmVyZW50bHkgdGhhbiBudW1lcmljIHZhbHVlcy4gRm9yIGluc3RhbmNlLCB3aGVuIHVzaW5nIHRoZSBgc3VtKClgIGZ1bmN0aW9uLCB5b3UgaGF2ZSB0byBleHBsaWNpdGx5IGluZGljYXRlIHRoYXQgeW91IHdhbnQgdG8gcmVtb3ZlIE5BcyBmcm9tIHRoZSBjb21wdXRhdGlvbiBvciBlbHNlIGl0IHdpbGwgY2F1c2UgYW4gZXJyb3IuDQpgYGB7cn0NCnYxIDwtIGMoMSxOQSwzLDksMTUsTkEpDQpzdW0odjEsIG5hLnJtID0gVCkNCmBgYA0KDQpUZXN0aW5nIGlmIGEgdmFsdWUgaXMgZXF1YWwgdG8gTkEgaXMgZGlmZmVyZW50IGZyb20gdGVzdGluZyBpZiBhIHZhbHVlIGlzIGVxdWFsIHRvIGEgbnVtZXJpYyBvciBzdHJpbmcgdmFsdWUuIFlvdSBtdXN0IHVzZSBpcy5uYSgpIHJhdGhlciB0aGFuIGA9PWAuDQpgYGB7cn0NCjQgPT0gTkEgIyBSZXR1cm5zIE5BDQppcy5uYSg0KSAjIFJldHVybnMgRkFMU0UuIA0KYGBgDQoNCg0KIyMgSWRlbnRpZnlpbmcgdGhlIFBhdHRlcm4gb2YgTWlzc2luZyBWYWx1ZXMNCldoZW4gZXhwbG9yaW5nIGEgbmV3IGRhdGFzZXQsIGl0J3Mgd29ydGh3aGlsZSB0byBpZGVudGlmeSB0aGUgcGF0dGVybiBvZiBtaXNzaW5nIHZhbHVlcy4gVGhlIGBzdW1tYXJ5KClgIGluY2x1ZGVzIHRoZSBudW1iZXIgb2YgbWlzc2luZyB2YWx1ZXMgZm9yIGVhY2ggY29sdW1uIGFsb25nIHdpdGggdGhlIHN1bW1hcnkgc3RhdGlzdGljcy4gDQpgYGB7cn0NCnN1bW1hcnkoajE3aSkNCmBgYA0KTm90aWNlIHRoYXQgdGhlIFByaWNlLCBUYXgsIGFuZCBUb3RhbER1ZSBjb2x1bW5zIGhhdmUgbWlzc2luZyB2YWx1ZXMuIFRoZSBvdGhlcnMgbnVtZXJpYyBjb2x1bW5zIGRvIG5vdC4gSW50ZXJlc3RpbmcgdGhhdCB0aGUgbnVtYmVyIG9mIG1pc3NpbmcgdmFsdWVzIGZvciBUYXggYW5kIFRvdGFsRHVlIGFyZSB0aGUgc2FtZS4gQSB2aXN1YWwgaW5zcGVjdGlvbiBjb25maXJtcyB0aGF0IHRoZXNlIGFyZSBhc3NvY2lhdGVkIHdpdGggdGhlIHNhbWUgb2JzZXJ2YXRpb25zLg0KDQojIyBSZW1vdmluZyBNaXNzaW5nIFZhbHVlcw0KSWYgYSBjb2x1bW4gaGFzIGEgbGFyZ2UgcGVyY2VudGFnZSBvZiBtaXNzaW5nIHZhbHVlcywgdGhlbiB5b3UgbWF5IHdhbnQgdG8gY29uc2lkZXIgZHJvcHBpbmcgdGhhdCBjb2x1bW4gYWxsIHRvZ2V0aGVyLiBIb3dldmVyLCBpZiB5b3UgYXJlIHJlYWxseSBpbnRlcmVzdGVkIGluIHRoZSBlZmZlY3Qgb2YgdGhhdCBjb2x1bW4sIHRoZW4gYSBiZXR0ZXIgaWRlYSBpcyB0byByZW1vdmUgdGhlIG9ic2VydmF0aW9ucyB0aGF0IGhhdmUgbWlzc2luZyB2YWx1ZXMuIFRvIGluZGljYXRlIHRoYXQgeW91IHdhbnQgdG8ga2VlcCBvYnNlcnZhdGlvbnMgZm9yIHdoaWNoIHRoZSB2YWx1ZSBpcyBOT1QgTkEsIHRoZW4gdXNlIHRoZSBleGNsYW1hdGlvbiBwb2ludCB0byBuZWdhdGUgdGhlIGBpcy5uYSgpYCBmdW5jdGlvbi4NCmBgYHtyfQ0KajE3aV8yIDwtIGoxN2kgJT4lDQogIGZpbHRlcighaXMubmEoUHJpY2UpKQ0Kc3VtbWFyeShqMTdpXzIpDQpgYGANCg0KTm90aWNlIHRoYXQgYnkgcmVtb3ZpbmcgdGhlIG9ic2VydmF0aW9ucyB3aXRoIG1pc3NpbmcgdmFsdWVzIGZvciBQcmljZSBtdXN0IGhhdmUgaW5jbHVkZWQgc29tZSBvYnNlcnZhdGlvbnMgZm9yIHdoaWNoIFRheCBhbmQgVG90YWxEdWUgd2VyZSBtaXNzaW5nIGJlY2F1c2UgdGhlcmUgYXJlIG9ubHkgMzI4IG1pc3NpbmcgdmFsdWVzIGluc3RlYWQgb2YgMzQxLg0KDQpJIHdvdWxkIGdpdmUgYSBxdWljayB2aXN1YWwgaW5zcGVjdGlvbiB0byB0aGUgb2JzZXJ2YXRpb25zIHRoYXQgYXJlIG1pc3NpbmcgdmFsdWVzIGZvciBUYXggdG8gc2VlIGlmIEkgY2FuIGlkZW50aWZ5IGEgcGF0dGVybi4gSWYgdGhlIHBhdHRlcm4gaXNuJ3QgbWVhbmluZ2Z1bCwgdGhlbiBJIHdvdWxkIGV4cG9ydCB0aGUgdHJhbnNhY3Rpb25zIGFuZCBnbyBvdmVyIHRoZW0gd2l0aCB0aGUgY2FzaGllciBvciBtYW5hZ2VyLg0KYGBge3J9DQptaXNzaW5nUHJpY2UgPC0gajE3aSAlPiUNCiAgZmlsdGVyKGlzLm5hKFByaWNlKSkNCmBgYA0KVGhlcmUgYXJlIGFsc28gZnVuY3Rpb25zIGluIG90aGVyIHBhY2thZ2VzIHRoYXQgaGVscCB5b3UgdmlzdWFsaXplIHRoZSBwYXR0ZXJuIG9mIG1pc3NpbmcgdmFsdWVzIHRvIGhlbHAgeW91IGdldCBhbiBpZGVhIG9mIHRoZSBwYXR0ZXJuIG9mIG1pc3NpbmcgdmFsdWVzLg0KDQojIyBJbXB1dGluZyBNaXNzaW5nIFZhbHVlcyB3aXRoIHRoZSBpZmVsc2UgRnVuY3Rpb24NCklmIHlvdSBkb24ndCB3YW50IHRvIGxvc2UgdGhlIG90aGVyIGluZm9ybWF0aW9uIGZyb20gdGhlIGluY29tcGxldGUgb2JzZXJ2YXRpb24sIHRoZW4geW91IGNhbiBmaWxsIGluIHRoZSBtaXNzaW5nIHZhbHVlcyB3aXRoIHRoZSBtZWFuIG9yIG1lZGlhbiB2YWx1ZSBmcm9tIHRoYXQgY29sdW1uLiBUbyBkbyB0aGF0LCB5b3UgY2FuIHVzZSB0aGUgYGlmZWxzZSgpYCBmdW5jdGlvbiwgd2hpY2ggaXMgaWRlbnRpY2FsIHRvIHRoZSBgPUlGKClgIGZ1bmN0aW9uIGluIEV4Y2VsLiBXZSdsbCBkbyB0aGF0IHdpdGhpbiB0aGUgbXV0YXRlIGZ1bmN0aW9uLCBhbmQgdGhlbiBjYWxjdWxhdGUgdGhlIHZhbHVlIGZvciBUb3RhbER1ZS4NCmBgYHtyfQ0KP2lmZWxzZQ0KajE3aV8zIDwtIGoxN2kgJT4lDQogIG11dGF0ZSgNCiAgICBUYXggPSBpZmVsc2UoaXMubmEoVGF4KSwgbWVhbihUYXgsIG5hLnJtID0gVCksIFRheCkgIyBDb3VsZCB1c2UgbWVkaWFuKCkgaW5zdGVhZCBvZiBtZWFuKCkgaWYgdGhlcmUgYXJlIG91dGxpZXJzDQogICAgLCBUb3RhbER1ZSA9IE5ldFRvdGFsICsgVGF4DQogICkNCnN1bW1hcnkoajE3aV8zKQ0KYGBgDQoNClRoZSBgaWZlbHNlYCBmdW5jdGlvbiBpcyBwb3dlcmZ1bCwgYW5kIHlvdSBjYW4gdXNlIGl0IGluIG1hbnkgb3RoZXIgd2F5cy4gQ29uZGl0aW9uYWwgc3RhdGVtZW50cyB3aWxsIGJlIGNvdmVyZWQgZWxzZXdoZXJlIGluIG1vcmUgZGVwdGguDQoNClRoZXJlIGFyZSBvdGhlciB3YXlzIGZvciBkZWFsaW5nIHdpdGggbWlzc2luZyB2YWx1ZXMuIEEgbW9kZWwtYmFzZWQgYXBwcm9hY2ggZmlsbHMgaW4gbWlzc2luZyB2YWx1ZXMgYmFzZWQgb24gdmFsdWVzIGZyb20gb25lIG9yIG1vcmUgb3RoZXIgY29sdW1ucy4NCg0KIyMgQ29uY2x1ZGluZyBDb21tZW50cw0KWW91IGNhbiBzZWUgdGhhdCB0aGUgZWFzaWVzdCBhcHByb2FjaCBmb3IgZGVhbGluZyB3aXRoIG1pc3NpbmcgdmFsdWVzIGlzIHRvIHJlbW92ZSB0aGUgY29sdW1ucyBvciB0aGUgb2JzZXJ2YXRpb25zOyBob3dldmVyLCB5b3UgbWF5IGJlIHRocm93aW5nIHRoZSBiYWJ5IG91dCB3aXRoIHRoZSBiYXRod2F0ZXIsIGVzcGVjaWFsbHkgd2l0aCBzbWFsbCBkYXRhc2V0cy4gVWx0aW1hdGVseSwgeW91J2xsIGhhdmUgdG8gdXNlIGp1ZGdtZW50IGZvciBkZWFsaW5nIHdpdGggbWlzc2luZyB2YWx1ZXMuIFRoZSBpbXBvcnRhbnQgdGhpbmcgaXMgdG8gZnVsbHkgZGlzY2xvc2Ugd2hhdCB5b3UgZG8uIA0KDQpUaGUgYmVzdCBhcHByb2FjaCBpcyB0byBwZXJmb3JtIHlvdXIgYW5hbHlzZXMgdXNpbmcgYm90aCBhcHByb2FjaGVzIGFuZCB0aGVuIGlkZW50aWZ5IGlmIHRoZSByZXN1bHRzIGNoYW5nZSBiYXNlZCBvbiB3aGF0IHlvdSBkby4gSWYgdGhlIHJlc3VsdHMgb2YgeW91ciBkbyBub3QgcXVhbGl0YXRpdmVseSBjaGFuZ2UsIHRoZW4gaXQncyBub3QgYSBiaWcgZGVhbC4gSWYgdGhlIHJlc3VsdHMgYXJlIGRpZmZlcmVudCwgdGhlbiB0aGluayBjYXJlZnVsbHkgYWJvdXQgd2hpY2ggYXBwcm9hY2ggaXMgYmVzdCBhbmQganVzdGlmeSB0aGUgYXBwcm9hY2ggdGhhdCB5b3UgdGhpbmsgaXMgYmVzdC4gDQoNCg0K