-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathData_Jobs_Cleaning.Rmd
439 lines (341 loc) · 14.7 KB
/
Data_Jobs_Cleaning.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
---
title: "Data Jobs Cleaning"
author: "Geovanny Risco y Robert Novak"
date: "28/12/2021"
output:
pdf_document:
toc: yes
toc_depth: '3'
html_document:
df_print: paged
number_sections: yes
toc: yes
toc_depth: 3
---
<!-- Para después de ejecutar el knitr ponerlo en la consola para cargar las variables rmarkdown::render("Informe.Rmd") -->
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo=T, message=F, warning=F)
```
\newpage
# Importación de librerías
```{r}
library(tidyverse)
#library(kableExtra)
library(pander) #Para mostrar los vectores más estéticamente
library(DT)
library(readr)
library(missForest)
library(car)
```
# Importación del dataset
```{r, cache=T}
raw_data <- read_csv("Data/Raw/uncleaned_data.csv")
```
```{r}
# raw_data %>% DT::datatable(
# extensions = 'FixedColumns',
# options = list(
# dom = 't',
# scrollX = TRUE,
# scrollCollapse = TRUE)
# )
```
# Exploración del dominio de las variables
## Nombre de todas las variables
```{r}
cols_raw_data <- names(raw_data)
cols_raw_data
```
## Selección de las variables de interés para el estudio del dominio
```{r}
cols_no_interest <- c("index","Job Description") #Eliminamos Job Description de momento por tener demasiada información
cols_raw_data_filtered <- cols_raw_data[-which(cols_raw_data %in% cols_no_interest)]
```
## Visualización en forma de lista del dominio de cada variable
```{r}
cols_raw_data_filtered %>%
map(function(x) unique(raw_data[[x]])) %>%
setNames(cols_raw_data_filtered) -> list_domain
list_domain
```
# Limpieza de datos
## Puntos a tener en cuenta para la limpieza de datos
A raíz del estudio hecho en el apartado anterior, hay que tener en cuenta los siguientes puntos para la limpieza de datos:
1. Se utiliza el `-1` para indicar valores faltantes. Adicionalmente, existen columnas que tienen un valor faltante que se representa de forma distinta a `-1` por la forma en la que se han extraído los datos. En la limpieza tendremos que tener en cuenta también esos casos y representar a todos los valores faltantes de forma homogénea mediante `NA`
1. La columna `Job title` tiene una gran diversidad de trabajos con una mínima variación que sería interesante tratarlos como un mismo trabajo. Para ello, habrá que definir un subconjunto de trabajos a partir del cuál tratar como iguales las variantes. Ese subconjunto será los que consideramos principales : \{ data scientist, data engineer, data analyst, machine learning, machine learning expert \}. Así, por ejemplo, un trabajo de e-commerce data analyst o uno de RFP data analyst será tratado bajo la categoría de data analyst
1. La variable `Company name` tiene la información del rating. Habrá que eliminar esa redundancia
1. Es interesante añadir una nueva variable binaria a partir de `Location` y `Headquarters` para ver aquellas ofertas de trabajo en la que la cede central de la empresa está en el mismo sitio que la oferta
1. Algunas variables como `Salary Estimate`, `Size` y `Revenue` contienen información que pueden ser aprovechadas mejor separándolas en más columnas a partir de las cuáles sacar más información.
1. `Salary Estimate` puede ser considerada una variable cuantitativa ya que, aunque se proporcione un rango variable para todas las ofertas, la realidad es que el salario no es un rango sino un valor concreto dado por un dominio continuo. La decisión que hemos tomado para solucionar esto es considerar el punto medio del rango proporcionado como el salario de la oferta. Esta solución es una aproximación ya que dos ofertas con mismos rangos tendrian el mismo salario y no tendría por qué ser considerados como el mismo. O, incluso dos salarios con rangos distintos pero con una cierta intersección podrían tener en realidad el mismo salario. Sin embargo, aunque lo ideal sería hacer un estudio externo sobre la distribución del salario dado el rango, la empresa particular, etc. Al no disponer de esa información asumimos esta simplificación.
1. `Size` y `Revenue` deben ser consideradas para análisis posteriores como variables ordinales ya que su dominio corresponde a categorías no solapadas en el que el orden importa.
## Tratamiento de la variable `Job Titles`
```{r}
clean_data <- raw_data
# Asignamos el valor NA en todas las celdas de la tabla donde aparece un -1 o un Unknown
# Esto lo podemos hacer porque entre las variables numéricas que tenemos no hay ninguna en la que en su dominio tenga valores negativos
clean_data[clean_data==-1 | clean_data=="Unknown" | clean_data=="Unknown / Non-Applicable"] <- NA
data_jobs_titles <- "data scientist|data engineer|data analyst|machine learning"
# Tratamos el nombre de los trabajos para considerar idénticos aquellos que tienen mínimas variaciones
clean_data <- clean_data %>%
mutate(`Job Title`= tolower(`Job Title`) %>%
str_extract(data_jobs_titles)
)
```
Ahora comprobemos qué tipo de trabajos se han quedado fuera:
```{r}
out_jobs_index <- clean_data[is.na(clean_data$`Job Title`),] %>%
select(index) %>%
as_vector() %>%
unname()
out_jobs <- raw_data %>%
filter(`index` %in% out_jobs_index) %>%
select(`Job Title`) %>%
distinct() %>%
datatable()
```
Consideramos que los trabajos que se quedan fuera son demasiado específicos para el análisis que queremos hacer posteriormente. Además, el número de observaciones que perderíamos si no consideraramos el estudio posterior para ninguna de estas profesiones es de `r length(out_jobs_index)` lo que consideramos una cifra asumible.
```{r}
clean_data <- clean_data[!is.na(clean_data$`Job Title`),]
```
## Tratamiento de la variable `Salary Estimate`
Esta variable es interesante separarla en dos: una para el rango mínimo y otra para el rango máximo. Adicionalmente, es interesante crear una nueva a partir de estas dos que sea el rango medio.
Para ello, como hay muchas observaciones diferentes de rangos distintos nos aseguramos de forma automatizada que todos los rangos están en miles:
```{r}
all_k <- raw_data %>%
select(`Salary Estimate`) %>%
distinct() %>%
map(function(x) grepl(".*K.*K",x)) %>%
as_vector() %>%
all()
if(all_k){
cat("Todas las observaciones están en miles, no hay que tener ningún cuidado especial")
}else{
cat("¡Cuidado! Existen algunas observaciones que no están en miles, hay que tratar esas observaciones")
}
```
```{r}
clean_data <- clean_data %>%
separate(`Salary Estimate`,sep="-", into=c("Salary Estimate Inf","Salary Estimate Sup")) %>%
mutate(
`Salary Estimate Inf` = gsub("K|\\$","",`Salary Estimate Inf`),
`Salary Estimate Sup` = gsub("K|\\$","",`Salary Estimate Sup`)
) %>%
#Lo separamos de nuevo para evitar poner todas las variaciones posibles Glassdoor est., Employer Est. etc
separate(`Salary Estimate Sup`, sep="\\(", into=c("Salary Estimate Sup", "drop")) %>%
select(-drop ) %>%
mutate(
`Salary Estimate Inf` = as.double(`Salary Estimate Inf`),
`Salary Estimate Sup` = as.double(`Salary Estimate Sup`),
`Salary Estimate Med` = (`Salary Estimate Inf` + `Salary Estimate Sup`)/2
)
```
## Tratamiento de la variable `Company Name`
```{r}
clean_data <- clean_data %>%
mutate(`Company Name`=str_remove_all(`Company Name`,"\n.*"))
```
## Tratamiento de la variable `Headquarters` y `Location`
```{r}
clean_data <- clean_data %>%
mutate(`Same Location Headquarter`= Location==Headquarters)
```
## Tratamiento de la variable `Size`
Esta variable será tratada de forma análoga a `Salary Estimate`.
```{r}
clean_data <- clean_data %>%
mutate(
`Size Ordered`=
case_when(
Size=="1 to 50 employees" ~ 1,
Size=="51 to 200 employees" ~ 2,
Size=="201 to 500 employees" ~ 3,
Size=="501 to 1000 employees" ~ 4,
Size=="1001 to 5000 employees" ~ 5,
Size=="5001 to 10000 employees" ~ 6,
Size=="10000+ employees" ~ 7,
),
Size=
case_when(
Size=="1 to 50 employees" ~ "1-50",
Size=="51 to 200 employees" ~ "51-200",
Size=="201 to 500 employees" ~ "201-500",
Size=="501 to 1000 employees" ~ "501-1000",
Size=="1001 to 5000 employees" ~ "1001-5000",
Size=="5001 to 10000 employees" ~ "5001-10000",
Size=="10000+ employees" ~ "10000-Inf",
)
) %>%
separate(Size, sep="-", into=c("Size Inf","Size Sup")) %>%
mutate(
`Size Inf`=as.numeric(`Size Inf`),
`Size Sup`=as.numeric(`Size Sup`)
)
```
## Tratamiento de la variable `Revenue`
Para esta variable, aparte de sacar los valores extremos, sería adecuada tratarla como una variable ordinal
```{r}
clean_data <- clean_data %>%
mutate(
`Revenue Ordered`=
case_when(
Revenue=="Less than $1 million (USD)" ~ 1,
Revenue=="$1 to $5 million (USD)" ~ 2,
Revenue=="$5 to $10 million (USD)" ~ 3,
Revenue=="$10 to $25 million (USD)" ~ 4,
Revenue=="$25 to $50 million (USD)" ~ 5,
Revenue=="$50 to $100 million (USD)" ~ 6,
Revenue=="$100 to $500 million (USD)" ~ 7,
Revenue=="$500 million to $1 billion (USD)" ~ 8,
Revenue=="$1 to $2 billion (USD)" ~ 9,
Revenue=="$2 to $5 billion (USD)" ~ 10,
Revenue=="$5 to $10 billion (USD)" ~ 11,
Revenue=="$10+ billion (USD)" ~ 12
),
Revenue=
case_when(
Revenue=="Less than $1 million (USD)" ~ "0-1000000",
Revenue=="$1 to $5 million (USD)" ~ "1000000-5000000",
Revenue=="$5 to $10 million (USD)" ~ "5000000-10000000",
Revenue=="$10 to $25 million (USD)" ~ "10000000-25000000",
Revenue=="$25 to $50 million (USD)" ~ "25000000-50000000",
Revenue=="$50 to $100 million (USD)" ~ "50000000-100000000",
Revenue=="$100 to $500 million (USD)" ~ "100000000-500000000",
Revenue=="$500 million to $1 billion (USD)" ~ "500000000-1000000000",
Revenue=="$1 to $2 billion (USD)" ~ "1000000000-2000000000",
Revenue=="$2 to $5 billion (USD)" ~ "2000000000-5000000000",
Revenue=="$5 to $10 billion (USD)" ~ "5000000000-10000000000",
Revenue=="$10+ billion (USD)" ~ "10000000000-Inf",
)
) %>%
separate(Revenue, sep="-", into=c("Revenue Inf","Revenue Sup")) %>%
mutate(
`Revenue Inf`=as.numeric(`Revenue Inf`),
`Revenue Sup`=as.numeric(`Revenue Sup`)
)
```
## Tratamiento de *outliers* de la variable `Salary Estimate Med`
```{r}
boxplot(clean_data$`Salary Estimate Med`, main="Salario estimado promedio", col="blue")
salary_outliers <- unique(boxplot.stats(clean_data$`Salary Estimate Med`)$out)
salary_outliers
clean_data %>%
filter(
`Salary Estimate Med` == 43.5
)
clean_data %>%
filter(
`Salary Estimate Med` == 271.5
)
```
## Integración
```{r}
clean_data <- clean_data %>%
select(-c("index"
,"Competitors"
,"Founded"
,"Company Name"
,"Type of ownership"))
clean_data %>%
write_csv("Data/Clean/clean_data.csv")
```
# Análisis
## Estudio de la normalidad de variable `Salary Estimate Med`
```{r}
n_bins=clean_data %>%
distinct(`Salary Estimate Med`) %>%
as_vector() %>%
length()
```
```{r}
histogram_salary <- clean_data %>%
ggplot(aes(x=`Salary Estimate Med`)) +
geom_histogram(bins=n_bins, fill="lightblue",color="black") +
theme_bw() +
scale_x_continuous(breaks=scales::pretty_breaks(n = 10)) +
scale_y_continuous(breaks=scales::pretty_breaks(n = 10)) +
labs(x="Salario medio estimado", y="Conteo")
```
```{r}
density_salary <- clean_data %>%
ggplot(aes(x=`Salary Estimate Med`)) +
geom_density(alpha=0.6, fill="lightblue") +
theme_bw() +
scale_x_continuous(breaks=scales::pretty_breaks(n = 10)) +
scale_y_continuous(breaks=scales::pretty_breaks(n = 10)) +
labs(x="Salario medio estimado", y="Probabilidad")
```
```{r}
normality_test_salary <- shapiro.test(clean_data$`Salary Estimate Med`)
```
```{r}
clean_data %>%
ggplot(aes(x=`Rating`)) +
geom_density(alpha=0.6, fill="lightblue") +
theme_bw() +
scale_x_continuous(breaks=scales::pretty_breaks(n = 10)) +
scale_y_continuous(breaks=scales::pretty_breaks(n = 10)) +
labs(x="Salario medio estimado", y="Probabilidad")
```
```{r}
shapiro.test(clean_data$Rating)
```
## Estudio de relación entre `Estimate Salary Med` y otras variables
### Categóricas: `Job Title`, `Size Ordered`, `Revenue Ordered` y `Industry`
```{r}
clean_data %>%
ggplot(aes(y=`Salary Estimate Med`, fill=`Job Title`)) +
scale_x_continuous(labels=NULL) +
scale_y_continuous(breaks=scales::pretty_breaks(n = 10)) +
theme_classic() +
geom_boxplot()
```
```{r}
levene_job <- leveneTest(`Salary Estimate Med` ~ `Job Title` , data=clean_data )$`Pr(>F)`[1]
levene_size <- leveneTest(`Salary Estimate Med` ~ as.factor(`Size Ordered`) , data=clean_data )$`Pr(>F)`[1]
levene_revenue <- leveneTest(`Salary Estimate Med` ~ as.factor(`Revenue Ordered`) , data=clean_data )$`Pr(>F)`[1]
levene_industry <- leveneTest(`Salary Estimate Med` ~ `Industry` , data=clean_data )$`Pr(>F)`[1]
```
```{r}
kruskal.test(`Salary Estimate Med` ~ `Job Title`, data = clean_data)
```
```{r}
kruskal.test(`Salary Estimate Med` ~ `Size Ordered`, data = clean_data)
```
```{r}
kruskal.test(`Salary Estimate Med` ~ `Revenue Ordered`, data = clean_data)
```
```{r}
kruskal.test(`Salary Estimate Med` ~ Industry, data = clean_data)
```
```{r}
# Estudio de las diferentes industrias en el dataset y sus concurrencias
sort(table(clean_data$Industry),descending=T)
length(unique(clean_data$Industry))
clean_data %>%
filter(!is.na(Industry)) %>%
count(Industry, sort=T)
clean_data %>%
filter(!is.na(Industry)) %>%
add_count(Industry) %>%
arrange(desc(n)) %>% # igual a add_count(Industry,sort=T)
slice_max(n, prop=0.5) %>%
ggplot(aes(x=Industry, y=`Salary Estimate Med`)) + geom_bar(stat="summary", fun="mean", fill="steelblue")+coord_flip()+theme_bw()
```
### Continuas: `Rating`
```{r}
clean_data %>%
ggplot(aes(x=Rating,y=`Salary Estimate Med`)) +
geom_point()
```
```{r}
cor(clean_data$Rating, clean_data$`Salary Estimate Med`, use="complete.obs")
```
### Binarias: `Same Location Headquarter`
```{r}
summary(glm(`Same Location Headquarter`~`Salary Estimate Med`,family=binomial(link=logit), data=clean_data))
```
```{r}
clean_data %>%
lm(`Salary Estimate Med` ~ `Job Title` + as.factor(`Size Ordered`)+ as.factor(`Revenue Ordered`) + `Industry` , data=.) %>%
summary()
```