Most Valued Data Science Skills

Author

Ozge Gundogan, Antik Salimullah, Andre Thomson

Published

October 20, 2026

1 Introduction

The goal of this project is to investigate which data science skills are requested most often in the supplied job-posting dataset.

Our main research question is: Which data science skills are most frequently requested by employers, and how do these requirements differ across data science roles?

We will analyze job postings to identify the most frequently requested technical skills and compare skill demand across Data Analyst, Data Engineer, and Data Scientist positions.

We will use the frequency of skills mentioned in job postings as our measure of skill demand. This will help us understand which skills employers commonly request.

Group Members: Ozge Gundogan, Antik Salimullah, Andre Thomson

Collaboration: We plan to use Gmail for group communication and GitHub for sharing code, documentation, and version control.

2 Planned Approach

We will use the “Data Science Job Postings & Skills (2024)” dataset from Kaggle for this project.

Data Source: https://www.kaggle.com/datasets/asaniczka/data-science-job-postings-and-skills

We selected this dataset because it contains information about job postings, required skills, job titles, companies, and locations. We will use two CSV files from this dataset for our analysis: job_postings.csv and job_skills.csv.

We will download the CSV files from Kaggle, upload them to GitHub, and load them into R using the read.csv() function. We will then examine the structure of the datasets and identify missing values and duplicate records. We will separate comma-separated skills stored in the same row into individual rows, standardize skill names, and check for duplicate job-skill combinations. We will also examine the number of skills listed in each job posting to identify potential outliers.

We will use SQLite to organize the cleaned data into three related tables:
- Jobs: Stores information about job postings.
- Skills: Stores unique skills.
- Job_Skills: Connects job postings with their required skills.

We will use job_link and skill_id to connect the three tables and represent the many-to-many relationship between jobs and skills. The ER diagram below illustrates our proposed database structure and key relationships.

erDiagram
    Jobs ||--o{ Job_Skills : requires
    Skills ||--o{ Job_Skills : includes

    Jobs {
        string job_link PK
        string job_title
        string company
        string job_location
        string job_level
        string job_type
    }

    Skills {
        int skill_id PK
        string skill_name
    }

    Job_Skills {
        string job_link PK,FK
        int skill_id PK,FK
    }

We will analyze the frequency of technical skills mentioned in job postings to identify the most frequently requested skills. We will also compare skill demand across three job categories: Data Analyst, Data Engineer, and Data Scientist. We will calculate the percentage of job postings requiring each skill within each job category to make comparisons more meaningful. We will then use the ggplot2 package in R to visualize the results.

3 Anticipated Challenges

One possible challenge is handling missing or incomplete data. Some job postings may contain empty fields or missing skill information.

Another challenge is cleaning and standardizing skill names. Since multiple skills are stored in a single column, we will need to separate them into individual records, standardize capitalization, and remove duplicate job-skill combinations.

4 Code Base

4.1 Required Packages

We will use R packages to load, clean, transform, and analyze the datasets.

library(tidyr)
library(dplyr)

Attaching package: 'dplyr'
The following objects are masked from 'package:stats':

    filter, lag
The following objects are masked from 'package:base':

    intersect, setdiff, setequal, union
library(ggplot2)
library(DBI)
library(RSQLite)

4.2 Load the Datasets

We will load the two CSV files from GitHub into R using read.csv().

job_postings_url <- "https://raw.githubusercontent.com/OzgeG01/DATA607-Assignment/refs/heads/main/job_postings.csv"
job_skills_url <- "https://raw.githubusercontent.com/OzgeG01/DATA607-Assignment/refs/heads/main/job_skills.csv"

job_postings <- read.csv(job_postings_url)
job_skills <- read.csv(job_skills_url)

head(job_postings)
                                                                                                     job_link
1         https://www.linkedin.com/jobs/view/senior-machine-learning-engineer-at-jobs-for-humanity-3804053819
2         https://www.linkedin.com/jobs/view/principal-software-engineer-ml-accelerators-at-aurora-3703455068
3    https://www.linkedin.com/jobs/view/senior-etl-data-warehouse-specialist-at-adame-services-llc-3765023888
4 https://www.linkedin.com/jobs/view/senior-data-warehouse-developer-architect-at-morph-enterprise-3794602483
5                                    https://www.linkedin.com/jobs/view/lead-data-engineer-at-dice-3805948138
6                 https://www.linkedin.com/jobs/view/senior-data-engineer-at-university-of-chicago-3798206502
            last_processed_time  last_status got_summary got_ner
1 2024-01-21 08:08:48.031964+00 Finished NER           t       t
2 2024-01-20 04:02:12.331406+00 Finished NER           t       t
3 2024-01-21 08:08:31.941595+00 Finished NER           t       t
4 2024-01-20 15:30:55.796572+00 Finished NER           t       t
5 2024-01-21 08:08:58.312124+00 Finished NER           t       t
6 2024-01-21 07:14:11.378097+00 Finished NER           t       t
  is_being_worked                                    job_title
1               f             Senior Machine Learning Engineer
2               f Principal Software Engineer, ML Accelerators
3               f         Senior ETL Data Warehouse Specialist
4               f  Senior Data Warehouse Developer / Architect
5               f                           Lead Data Engineer
6               f                         Senior Data Engineer
                company      job_location first_seen  search_city
1     Jobs for Humanity     New Haven, CT 2024-01-14   East Haven
2                Aurora San Francisco, CA 2024-01-14   El Cerrito
3    Adame Services LLC      New York, NY 2024-01-14   Middletown
4      Morph Enterprise    Harrisburg, PA 2024-01-12      Lebanon
5                  Dice         Plano, TX 2024-01-14     McKinney
6 University of Chicago       Chicago, IL 2024-01-14 East Chicago
  search_country                search_position  job_level job_type
1  United States Agricultural-Research Engineer Mid senior   Onsite
2  United States                 Set-Key Driver Mid senior   Onsite
3  United States   Technical Support Specialist  Associate   Onsite
4  United States                      Architect Mid senior   Onsite
5  United States       Maintenance Data Analyst Mid senior   Onsite
6  United States        Data Base Administrator Mid senior   Onsite
head(job_skills)
                                                                                                     job_link
1         https://www.linkedin.com/jobs/view/senior-machine-learning-engineer-at-jobs-for-humanity-3804053819
2         https://www.linkedin.com/jobs/view/principal-software-engineer-ml-accelerators-at-aurora-3703455068
3    https://www.linkedin.com/jobs/view/senior-etl-data-warehouse-specialist-at-adame-services-llc-3765023888
4 https://www.linkedin.com/jobs/view/senior-data-warehouse-developer-architect-at-morph-enterprise-3794602483
5                                    https://www.linkedin.com/jobs/view/lead-data-engineer-at-dice-3805948138
6                 https://www.linkedin.com/jobs/view/senior-data-engineer-at-university-of-chicago-3798206502
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           job_skills
1                                                                                                                                                                                                                                                                                                                                  Machine Learning, Programming, Python, Scala, Java, Data Engineering, Distributed Computing, Statistical Modeling, Optimization, Data Pipelines, Cloud Computing, DevOps, Software Development, Data Gathering, Data Preparation, Data Visualization, Machine Learning Frameworks, scikitlearn, PyTorch, Dask, Spark, TensorFlow, Distributed File Systems, Multi node Database Paradigms, Open Source ML Software, Responsible AI, Explainable AI
2                                                                                                                                                                                                                                                                                                                                                    C++, Python, PyTorch, TensorFlow, MXNet, CUDA, OpenCL, OpenVX, Halide, SIMD programming models, MLspecific accelerators, Linux/unix environments, Deep learning frameworks, Computer vision deep learning models, ML software and hardware technology, Inference on edge platforms, Cloud ML training pipelines, HPC experience, Performance troubleshooting, Profiling, Roofline model, Analytical skills, Communication skills
3                                                                                                                                                                                                                                                                                                                      ETL, Data Integration, Data Transformation, Data Warehousing, Business Intelligence, Data Modeling, Data Architecture, Data Quality, Data Validation, Data Cleansing, Performance Optimization, Performance Tuning, Troubleshooting, Documentation, Reporting, Data Analysis, Collaboration, Communication, SQL, Informatica, Talend, Apache NiFi, AWS Redshift, Azure SQL Data Warehouse, Financial/Banking, CloudBased Data Platforms, Regulatory Compliance
4 Data Lakes, Data Bricks, Azure Data Factory Pipelines, Spark, Python, Business Intelligence, Data Warehouse, SQL Server, Azure, ETL/ELT, SQL Server Integration Services, TSQL, Data Formatting, Data Capture, Data Search, Data Retrieval, Data Extraction, Data Classification, Information Filtering, Data Mining Architectures, Modeling Standards, Reporting, Data Analysis Methodologies, Data Engineering, Database File Systems Optimization, API's, Analytics as a Service, Relational Databases, Dimensional Databases, Entity Relationships, Data Warehousing, Facts, Dimensions, Star Schema Concepts, Star Schema Terminology, Project Management, Organizational Skills, Collaboration, Communication, Technical Presentaion Skills, 12+ Years of Relevant Experience
5                                                                                                                                                                                                                                                                                                                                                                     Java, Scala, Python, RDBMS, NoSQL, Redshift, Snowflake, Unit testing, Agile engineering, Big data technologies, Cloud computing (AWS Microsoft Azure Google Cloud), Distributed data/computing tools (MapReduce Hadoop Hive EMR Kafka Spark Gurobi MySQL), Realtime data and streaming applications, NoSQL implementation (Mongo Cassandra), Data warehousing (Redshift Snowflake), UNIX/Linux, Shell scripting
6                                                                                                                   Data Warehouse (DW), Extract/Transform/Load (ETL), Oracle, VPD, MuleSoft, Cloudera Apache Hadoop Ecosystem, Hadoop, Java, Python, R, AI/ML, Predictive Analytics, Business Objects, Tableau, OBIA/OBIEE, Oracle Cloud Financials, Power Designer, Erwin, UNIX, ODBC, JDBC, Perl DBI, Shell Scripting, PL/SQL, SQL Developer, SQL Plus, SQL Loader, TOAD, Data Profiling, SourceTarget Mapping, Transformations, Business Rules, OWB, ODI, Data Stage, Informatica, SQL, Unix Server, Windows Workstation, Windows Server, Microsoft Office, Excel, Analytic Skills, ProblemSolving, Communication Skills, Teamwork, Accountability, Attention to Detail, Accuracy

4.3 Examining Dataset Structure

Before cleaning the data, we will examine the structure of both datasets using the str() function. This will help us understand the number of observations, variable names, data types, and sample values.

str(job_postings)
'data.frame':   12217 obs. of  15 variables:
 $ job_link           : chr  "https://www.linkedin.com/jobs/view/senior-machine-learning-engineer-at-jobs-for-humanity-3804053819" "https://www.linkedin.com/jobs/view/principal-software-engineer-ml-accelerators-at-aurora-3703455068" "https://www.linkedin.com/jobs/view/senior-etl-data-warehouse-specialist-at-adame-services-llc-3765023888" "https://www.linkedin.com/jobs/view/senior-data-warehouse-developer-architect-at-morph-enterprise-3794602483" ...
 $ last_processed_time: chr  "2024-01-21 08:08:48.031964+00" "2024-01-20 04:02:12.331406+00" "2024-01-21 08:08:31.941595+00" "2024-01-20 15:30:55.796572+00" ...
 $ last_status        : chr  "Finished NER" "Finished NER" "Finished NER" "Finished NER" ...
 $ got_summary        : chr  "t" "t" "t" "t" ...
 $ got_ner            : chr  "t" "t" "t" "t" ...
 $ is_being_worked    : chr  "f" "f" "f" "f" ...
 $ job_title          : chr  "Senior Machine Learning Engineer" "Principal Software Engineer, ML Accelerators" "Senior ETL Data Warehouse Specialist" "Senior Data Warehouse Developer / Architect" ...
 $ company            : chr  "Jobs for Humanity" "Aurora" "Adame Services LLC" "Morph Enterprise" ...
 $ job_location       : chr  "New Haven, CT" "San Francisco, CA" "New York, NY" "Harrisburg, PA" ...
 $ first_seen         : chr  "2024-01-14" "2024-01-14" "2024-01-14" "2024-01-12" ...
 $ search_city        : chr  "East Haven" "El Cerrito" "Middletown" "Lebanon" ...
 $ search_country     : chr  "United States" "United States" "United States" "United States" ...
 $ search_position    : chr  "Agricultural-Research Engineer" "Set-Key Driver" "Technical Support Specialist" "Architect" ...
 $ job_level          : chr  "Mid senior" "Mid senior" "Associate" "Mid senior" ...
 $ job_type           : chr  "Onsite" "Onsite" "Onsite" "Onsite" ...

The job_postings dataset contains 12,217 observations and 15 variables. All variables are stored as character values. Key variables for our analysis include job links, job titles, companies, job locations, job levels, and job types.

str(job_skills)
'data.frame':   12217 obs. of  2 variables:
 $ job_link  : chr  "https://www.linkedin.com/jobs/view/senior-machine-learning-engineer-at-jobs-for-humanity-3804053819" "https://www.linkedin.com/jobs/view/principal-software-engineer-ml-accelerators-at-aurora-3703455068" "https://www.linkedin.com/jobs/view/senior-etl-data-warehouse-specialist-at-adame-services-llc-3765023888" "https://www.linkedin.com/jobs/view/senior-data-warehouse-developer-architect-at-morph-enterprise-3794602483" ...
 $ job_skills: chr  "Machine Learning, Programming, Python, Scala, Java, Data Engineering, Distributed Computing, Statistical Modeli"| __truncated__ "C++, Python, PyTorch, TensorFlow, MXNet, CUDA, OpenCL, OpenVX, Halide, SIMD programming models, MLspecific acce"| __truncated__ "ETL, Data Integration, Data Transformation, Data Warehousing, Business Intelligence, Data Modeling, Data Archit"| __truncated__ "Data Lakes, Data Bricks, Azure Data Factory Pipelines, Spark, Python, Business Intelligence, Data Warehouse, SQ"| __truncated__ ...

The job_skills dataset contains 12,217 observations and 2 variables: job_link and job_skills. Both variables are stored as character values. The job_skills column contains multiple skills separated by commas, which will need to be separated into individual records during data cleaning.

4.4 Checking Missing Values

Before cleaning the datasets, we will check for missing values in each column. We will examine both NA values and empty strings because missing information may be represented in different ways.

colSums(is.na(job_postings))
           job_link last_processed_time         last_status         got_summary 
                  0                   0                   0                   0 
            got_ner     is_being_worked           job_title             company 
                  0                   0                   0                   0 
       job_location          first_seen         search_city      search_country 
                  0                   0                   0                   0 
    search_position           job_level            job_type 
                  0                   0                   0 
colSums(is.na(job_skills))
  job_link job_skills 
         0          0 

The results show that neither dataset contains any NA values.

colSums(job_postings == "", na.rm = TRUE)
           job_link last_processed_time         last_status         got_summary 
                  0                   0                   0                   0 
            got_ner     is_being_worked           job_title             company 
                  0                   0                   0                   0 
       job_location          first_seen         search_city      search_country 
                  1                   0                   0                   0 
    search_position           job_level            job_type 
                  0                   0                   0 
colSums(job_skills == "", na.rm = TRUE)
  job_link job_skills 
         0          5 

The empty string check identified one missing job location in the job_postings dataset and five missing skill entries in the job_skills dataset. All other columns contain no empty strings.

4.5 Handling Missing Values

We identified one empty value in the job_location column. Since our analysis focuses on the skills requested by employers rather than job locations, this missing value does not affect our main analysis. Therefore, instead of removing the job posting and losing its skill information, we will replace the empty location with “Unknown”.

job_postings$job_location[job_postings$job_location == ""] <- "Unknown"

We identified five job postings with missing skill information. Since our analysis focuses on identifying the most frequently requested skills, we will exclude these five postings from both datasets used for analysis. Before removing these records, we will identify their corresponding job_link values. This will allow us to locate the same job postings in the job_postings dataset and remove them from both datasets.

empty_skills <- job_skills[job_skills$job_skills == "", ]

empty_skills
                                                                                                                           job_link
671                     https://www.linkedin.com/jobs/view/mltc-nurse-care-manager-brooklyn-office-at-riverspring-living-3781600873
7899              https://www.linkedin.com/jobs/view/high-school-speech-language-pathologist-fmla-at-anthromed-education-3805736339
9599                                              https://uk.linkedin.com/jobs/view/notam-aviation-data-operator-at-nats-3800905866
10841 https://www.linkedin.com/jobs/view/mltc-nurse-care-manager-bilingual-russian-brooklyn-office-at-riverspring-living-3718500026
10847                        https://www.linkedin.com/jobs/view/medical-lab-technician-mlt-prn-at-lifepoint-health%C2%AE-3651076194
      job_skills
671             
7899            
9599            
10841           
10847           

After identifying the five records with missing skill information, we will use their job_link values to remove the corresponding job postings from both datasets. We will create cleaned copies to preserve the original data.

job_skills_clean <- job_skills[
  !(job_skills$job_link %in% empty_skills$job_link), 
]

job_postings_clean <- job_postings[
  !(job_postings$job_link %in% empty_skills$job_link),
]

After removing the five job postings with missing skill information, we will check both cleaned datasets to verify that no empty values remain.

colSums(job_postings_clean == "", na.rm = TRUE)
           job_link last_processed_time         last_status         got_summary 
                  0                   0                   0                   0 
            got_ner     is_being_worked           job_title             company 
                  0                   0                   0                   0 
       job_location          first_seen         search_city      search_country 
                  0                   0                   0                   0 
    search_position           job_level            job_type 
                  0                   0                   0 
colSums(job_skills_clean == "", na.rm = TRUE)
  job_link job_skills 
         0          0 

The results confirm that no empty strings remain in either cleaned dataset.

4.6 Checking Duplicate Records

We will check for duplicate job_link values in both cleaned datasets because job_link will be used to connect job postings with their required skills. Duplicate links could cause some skills to be counted more than once and affect the accuracy of our analysis.

sum(duplicated(job_postings_clean$job_link))
[1] 0
sum(duplicated(job_skills_clean$job_link))
[1] 0

The results show that there are no duplicate job_link values in either cleaned dataset, each job posting has a unique link.

4.7 Separating Job Skills

The job_skills column contains multiple skills separated by commas. We will separate these skills into individual rows so that each row represents one skill associated with a job posting. This will allow us to count and analyze the frequency of each skill.

job_skills_long <- job_skills_clean %>%
  separate_rows(job_skills, sep = ",")

head(job_skills_long, 10)
# A tibble: 10 × 2
   job_link                                                           job_skills
   <chr>                                                              <chr>     
 1 https://www.linkedin.com/jobs/view/senior-machine-learning-engine… "Machine …
 2 https://www.linkedin.com/jobs/view/senior-machine-learning-engine… " Program…
 3 https://www.linkedin.com/jobs/view/senior-machine-learning-engine… " Python" 
 4 https://www.linkedin.com/jobs/view/senior-machine-learning-engine… " Scala"  
 5 https://www.linkedin.com/jobs/view/senior-machine-learning-engine… " Java"   
 6 https://www.linkedin.com/jobs/view/senior-machine-learning-engine… " Data En…
 7 https://www.linkedin.com/jobs/view/senior-machine-learning-engine… " Distrib…
 8 https://www.linkedin.com/jobs/view/senior-machine-learning-engine… " Statist…
 9 https://www.linkedin.com/jobs/view/senior-machine-learning-engine… " Optimiz…
10 https://www.linkedin.com/jobs/view/senior-machine-learning-engine… " Data Pi…

The results show that the skills were successfully separated into individual rows. Each row now contains one job link and one skill.

4.8 Standardizing Skill Names

We will remove extra spaces at the beginning and end of skill names and convert all skill names to lowercase to ensure accurate counting. This will prevent the same skill from being counted separately due to differences in capitalization or spacing.

While standardizing skill names, we encountered an error caused by invalid UTF-8 characters in the dataset. To resolve this issue, we will convert the text to UTF-8 encoding and remove any characters that cannot be converted.

job_skills_long <- job_skills_long %>%
  mutate(
    job_skills = iconv(job_skills, from = "", to = "UTF-8", sub = ""),
    job_skills = tolower(trimws(job_skills))
  )

head(job_skills_long, 10)
# A tibble: 10 × 2
   job_link                                                           job_skills
   <chr>                                                              <chr>     
 1 https://www.linkedin.com/jobs/view/senior-machine-learning-engine… machine l…
 2 https://www.linkedin.com/jobs/view/senior-machine-learning-engine… programmi…
 3 https://www.linkedin.com/jobs/view/senior-machine-learning-engine… python    
 4 https://www.linkedin.com/jobs/view/senior-machine-learning-engine… scala     
 5 https://www.linkedin.com/jobs/view/senior-machine-learning-engine… java      
 6 https://www.linkedin.com/jobs/view/senior-machine-learning-engine… data engi…
 7 https://www.linkedin.com/jobs/view/senior-machine-learning-engine… distribut…
 8 https://www.linkedin.com/jobs/view/senior-machine-learning-engine… statistic…
 9 https://www.linkedin.com/jobs/view/senior-machine-learning-engine… optimizat…
10 https://www.linkedin.com/jobs/view/senior-machine-learning-engine… data pipe…

We will further standardize skill names by combining different expressions that refer to the same skill. For example, “powerbi” and “power bi” will be treated as one skill. This will help prevent duplicate categories and improve the accuracy of our skill frequency analysis.

job_skills_long <- job_skills_long %>%
  mutate(
    job_skills = case_when(
      job_skills %in% c("powerbi", "power-bi", "power bi") ~ "power bi",
      job_skills %in% c("communication skills", "communication") ~ "communication",
      job_skills %in% c("ms excel", "microsoft excel", "excel") ~ "excel",
      job_skills %in% c("ms sql server", "microsoft sql server") ~ "sql server",
      job_skills %in% c("java script", "javascript") ~ "javascript",
      job_skills %in% c("scikitlearn", "scikit learn", "scikit-learn") ~ "scikit-learn",
      job_skills %in% c("data bricks", "databricks") ~ "databricks",
      job_skills %in% c("tsql", "t-sql") ~ "t-sql",
      job_skills %in% c("cloudbased data platforms", "cloud-based data platforms") ~ "cloud-based data platforms",
      job_skills %in% c("mlspecific accelerators", "ml-specific accelerators") ~ "ml-specific accelerators",
      job_skills %in% c("problem solving", "problemsolving", "problem-solving") ~ "problem solving",
      TRUE ~ job_skills
    )
  )

4.9 Removing Non-Skill Entries

Some entries in the skills dataset describe job requirements rather than actual skills, such as years of experience, work shifts, and educational qualifications. We will remove these entries from the skill-level data to improve the accuracy of our analysis.

rows_before <- nrow(job_skills_long)

job_skills_long <- job_skills_long %>%
  filter(
    !grepl("\\b(year|years)\\b.*\\bexperience\\b|\\bexperience\\b.*\\b(year|years)\\b",
           job_skills),
    !grepl("\\b(hour|hours)\\b.*\\bshift\\b|\\bshift\\b.*\\b(hour|hours)\\b",
           job_skills),
    !grepl(
      "degree|diploma|bachelor|phd|doctorate|doctoral|baccalaureate|\\bmasters\\b|\\bged\\b",
      job_skills
    )
  )

rows_before - nrow(job_skills_long)
[1] 5331

A total of 5,331 entries related to experience requirements, work shifts, and educational qualifications were removed from the skill-level dataset. This filtering step helps focus the analysis on actual skills rather than general job requirements.

4.10 Checking and Removing Duplicate Job-Skill Pairs After Transformation

We will check whether the same skill appears more than once for a single job posting. Duplicate job-skill pairs could cause some skills to be counted multiple times and affect the accuracy of our analysis.

sum(duplicated(job_skills_long))
[1] 2749

We identified 2,749 duplicate job-skill pairs. We will remove these duplicates to ensure that each skill is counted only once per job posting.

job_skills_long <- job_skills_long %>%
  distinct(job_link, job_skills, .keep_all = TRUE)

sum(duplicated(job_skills_long))
[1] 0

After removing the duplicate job-skill pairs, the verification returned 0. This confirms that each skill now appears only once per job posting.

4.11 Checking Outliers in the Number of Skills per Job Posting

We will calculate the number of skills listed in each job posting and examine unusually high values. This will help us evaluate the quality of our data.

skills_per_job <- job_skills_long %>%
  count(job_link, name = "skill_count")

summary(skills_per_job$skill_count)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   1.00   17.00   23.00   25.13   30.00  218.00 

The results show that job postings contain an average of 25.13 skills. The maximum is 218 skills, which is unusually high compared to the typical number of skills per posting. We will investigate potential outliers.

We will use the Interquartile Range (IQR) method to identify potential outliers. IQR measures the spread of the middle 50% of the data (Q3 - Q1). We will calculate the upper limit using Q3 + 1.5 * IQR and count how many job postings exceed this limit.

Q1 <- quantile(skills_per_job$skill_count, 0.25)
Q3 <- quantile(skills_per_job$skill_count, 0.75)

upper_limit <- unname(Q3 + 1.5 * (Q3 - Q1))

upper_limit
[1] 49.5

Using the IQR method, we calculated an upper limit of 49.5 skills per job posting. Job postings with more than 49.5 skills will be considered potential outliers. We will count how many job postings exceed the upper limit of 49.5 skills to determine the number of potential outliers in our dataset.

sum(skills_per_job$skill_count > upper_limit)
[1] 468

We identified 468 job postings with more than 49.5 skills. These postings are considered potential outliers based on the IQR method. However, a high number of skills does not necessarily indicate incorrect data, so we will examine these records before deciding whether to remove them.

skills_per_job %>%
  arrange(desc(skill_count)) %>%
  head(10)
# A tibble: 10 × 2
   job_link                                                          skill_count
   <chr>                                                                   <int>
 1 https://www.linkedin.com/jobs/view/data-engineer-iv-max-digital-…         218
 2 https://www.linkedin.com/jobs/view/manager-data-center-operation…         175
 3 https://www.linkedin.com/jobs/view/manager-data-center-operation…         175
 4 https://www.linkedin.com/jobs/view/sr-manager-data-access-monito…         157
 5 https://www.linkedin.com/jobs/view/it-delivery-manager-%E2%80%93…         156
 6 https://www.linkedin.com/jobs/view/critical-facilities-operation…         138
 7 https://www.linkedin.com/jobs/view/u103-mls-field-mechanic-b-at-…         138
 8 https://www.linkedin.com/jobs/view/data-reporting-and-analytics-…         134
 9 https://www.linkedin.com/jobs/view/data-scientist-at-presbyteria…         124
10 https://www.linkedin.com/jobs/view/geospatial-intelligence-integ…         122

The 10 job postings with the highest skill counts contain between 122 and 218 skills, which is considerably higher than the median of 23. We will examine these postings further before deciding whether to keep or remove them.

top_job <- skills_per_job %>%
  arrange(desc(skill_count)) %>%
  slice(1)

job_postings_clean %>%
  filter(job_link == top_job$job_link) %>%
  select(job_title, company, job_level)
                                          job_title      company  job_level
1 Data Engineer IV - Max Digital (Data Engineering) ACV Auctions Mid senior
job_skills_long %>%
  filter(job_link == top_job$job_link) %>%
  select(job_skills) %>%
  head(50)
# A tibble: 50 × 1
   job_skills                    
   <chr>                         
 1 computer science              
 2 information technology        
 3 computer information systems  
 4 management information systems
 5 sql                           
 6 etl                           
 7 aws                           
 8 mongodb                       
 9 dbt                           
10 airflow                       
# ℹ 40 more rows

We examined the job posting with the highest skill count (218 skills). The position is Data Engineer IV at ACV Auctions. The displayed skills include relevant technical skills such as SQL, AWS, and ETL. This suggests that the high skill count may reflect the broad technical requirements of the position rather than a data error.

We decided to retain all 468 potential outliers because unusually high skill counts do not necessarily indicate data errors. Removing these postings could result in the loss of potentially valuable information about skill demand.

4.12 Creating a Relational Database with SQLite

We will connect to a SQLite database to store our cleaned data in three normalized tables: Jobs, Skills, and Job_Skills. To avoid errors when running the code multiple times, we will remove any existing versions of these tables before creating them again with the updated data.

con <- dbConnect(RSQLite::SQLite(), "data_science_skills.sqlite")

dbExecute(con, "DROP TABLE IF EXISTS Job_Skills")
[1] 0
dbExecute(con, "DROP TABLE IF EXISTS Skills")
[1] 0
dbExecute(con, "DROP TABLE IF EXISTS Jobs")
[1] 0
dbListTables(con)
character(0)

The database was successfully prepared for rebuilding. The result character(0) confirms that no tables remain before creating the updated Jobs, Skills, and Job_Skills tables.

4.12.1 Enabling Foreign Key Constraints

Before creating the relational tables, we will enable foreign key constraints in SQLite to maintain referential integrity between the tables.

dbExecute(con, "PRAGMA foreign_keys = ON;")
[1] 0

The command executed successfully and returned 0, indicating that no rows were affected.

We will verify that foreign key constraints are enabled in our SQLite database.

dbGetQuery(con, "PRAGMA foreign_keys;")
  foreign_keys
1            1

The result returned 1, confirming that foreign key constraints are enabled in our SQLite database.

4.12.2 Preparing the Jobs Data

We will prepare the Jobs table using the cleaned job postings dataset. This table will contain the job link, job title, company, location, job level, and job type. The job link will serve as the primary key because each job posting has a unique link.

jobs <- job_postings_clean %>%
  select(job_link, job_title, company,
         job_location, job_level, job_type)

head(jobs)
                                                                                                     job_link
1         https://www.linkedin.com/jobs/view/senior-machine-learning-engineer-at-jobs-for-humanity-3804053819
2         https://www.linkedin.com/jobs/view/principal-software-engineer-ml-accelerators-at-aurora-3703455068
3    https://www.linkedin.com/jobs/view/senior-etl-data-warehouse-specialist-at-adame-services-llc-3765023888
4 https://www.linkedin.com/jobs/view/senior-data-warehouse-developer-architect-at-morph-enterprise-3794602483
5                                    https://www.linkedin.com/jobs/view/lead-data-engineer-at-dice-3805948138
6                 https://www.linkedin.com/jobs/view/senior-data-engineer-at-university-of-chicago-3798206502
                                     job_title               company
1             Senior Machine Learning Engineer     Jobs for Humanity
2 Principal Software Engineer, ML Accelerators                Aurora
3         Senior ETL Data Warehouse Specialist    Adame Services LLC
4  Senior Data Warehouse Developer / Architect      Morph Enterprise
5                           Lead Data Engineer                  Dice
6                         Senior Data Engineer University of Chicago
       job_location  job_level job_type
1     New Haven, CT Mid senior   Onsite
2 San Francisco, CA Mid senior   Onsite
3      New York, NY  Associate   Onsite
4    Harrisburg, PA Mid senior   Onsite
5         Plano, TX Mid senior   Onsite
6       Chicago, IL Mid senior   Onsite

4.12.3 Creating the Jobs Table in SQLite

We will create the Jobs table in SQLite and define job_link as the primary key to ensure that each job posting is uniquely identified. Then, we will insert the cleaned job posting records into the table.

dbExecute(con, "
  CREATE TABLE Jobs (
    job_link TEXT PRIMARY KEY,
    job_title TEXT,
    company TEXT,
    job_location TEXT,
    job_level TEXT,
    job_type TEXT
  )
")
[1] 0
dbAppendTable(con, "Jobs", jobs)
[1] 12212
dbGetQuery(con, "SELECT COUNT(*) AS total_jobs FROM Jobs")
  total_jobs
1      12212

The Jobs table was successfully created in SQLite with job_link as the primary key. A total of 12,212 job postings were inserted into the table.

4.12.4 Preparing the Skills Data

We will create the Skills table by extracting unique skill names from job_skills_long. Each skill will be assigned a unique skill_id, which will serve as the primary key in our relational database.

skills_table <- data.frame(
  skill_id = seq_along(unique(job_skills_long$job_skills)),
  skill_name = unique(job_skills_long$job_skills)
)

head(skills_table, 10)
   skill_id            skill_name
1         1      machine learning
2         2           programming
3         3                python
4         4                 scala
5         5                  java
6         6      data engineering
7         7 distributed computing
8         8  statistical modeling
9         9          optimization
10       10        data pipelines

4.12.5 Creating the Skills Table in SQLite

We will create the Skills table in SQLite using skill_id as the primary key. Then, we will insert the unique skill names into the table.

dbExecute(con, "
  CREATE TABLE Skills (
    skill_id INTEGER PRIMARY KEY,
    skill_name TEXT NOT NULL UNIQUE
  )
")
[1] 0
dbAppendTable(con, "Skills", skills_table)
[1] 62752
dbGetQuery(con, "SELECT COUNT(*) AS total_skills FROM Skills")
  total_skills
1        62752

The Skills table was successfully created in SQLite with skill_id as the primary key. A total of 62,752 unique skills were inserted into the table.

4.12.6 Preparing the Job_Skills Data

We will create the Job_Skills table by matching each skill in job_skills_long with its corresponding skill_id from the Skills table. This table will connect job postings and skills using their identifiers.

job_skills_table <- job_skills_long %>%
  left_join(skills_table, by = c("job_skills" = "skill_name")) %>%
  select(job_link, skill_id)

head(job_skills_table, 10)
# A tibble: 10 × 2
   job_link                                                             skill_id
   <chr>                                                                   <int>
 1 https://www.linkedin.com/jobs/view/senior-machine-learning-engineer…        1
 2 https://www.linkedin.com/jobs/view/senior-machine-learning-engineer…        2
 3 https://www.linkedin.com/jobs/view/senior-machine-learning-engineer…        3
 4 https://www.linkedin.com/jobs/view/senior-machine-learning-engineer…        4
 5 https://www.linkedin.com/jobs/view/senior-machine-learning-engineer…        5
 6 https://www.linkedin.com/jobs/view/senior-machine-learning-engineer…        6
 7 https://www.linkedin.com/jobs/view/senior-machine-learning-engineer…        7
 8 https://www.linkedin.com/jobs/view/senior-machine-learning-engineer…        8
 9 https://www.linkedin.com/jobs/view/senior-machine-learning-engineer…        9
10 https://www.linkedin.com/jobs/view/senior-machine-learning-engineer…       10

The Job_Skills table was successfully created. Each row contains a job link and its corresponding skill ID, allowing us to connect job postings with their required skills.

4.12.7 Creating the Job_Skills Table in SQLite

We will create the Job_Skills table in SQLite using job_link and skill_id as foreign keys referencing the Jobs and Skills tables. Together, these columns will form a composite primary key to prevent duplicate job-skill pairs.

dbExecute(con, "
  CREATE TABLE Job_Skills (
    job_link TEXT NOT NULL,
    skill_id INTEGER NOT NULL,
    PRIMARY KEY (job_link, skill_id),
    FOREIGN KEY (job_link) REFERENCES Jobs(job_link),
    FOREIGN KEY (skill_id) REFERENCES Skills(skill_id)
  )
")
[1] 0
dbAppendTable(con, "Job_Skills", job_skills_table)
[1] 306870
dbGetQuery(con, "SELECT COUNT(*) AS total_job_skills FROM Job_Skills")
  total_job_skills
1           306870

The Job_Skills table was successfully created with a composite primary key and foreign keys referencing the Jobs and Skills tables. A total of 306,870 job-skill relationships were inserted.

4.12.8 Verifying Referential Integrity

We will verify referential integrity to ensure that all job links and skill IDs in the Job_Skills table correctly reference existing records in the Jobs and Skills tables.

dbGetQuery(con, "PRAGMA foreign_key_check;")
[1] table  rowid  parent fkid  
<0 rows> (or 0-length row.names)

The foreign key check returned 0 rows, confirming that there are no foreign key violations in the database. All job-skill relationships correctly reference existing records in the Jobs and Skills tables.

4.13 Exploratory Data Analysis

4.13.1 Most Frequently Requested Technical Skills

To identify the most frequently requested technical skills, we will first exclude non-technical skills and general competencies from the dataset.

non_technical_skills <- c(
  "communication",
  "problem solving",
  "project management",
  "teamwork",
  "analytical skills",
  "attention to detail",
  "collaboration",
  "leadership",
  "agile",
  "troubleshooting",
  "data science",
  "computer science",
  "time management",
  "engineering",
  "interpersonal skills"
)

We will load the Jobs, Skills, and Job_Skills tables from our SQLite database into R.

jobs_r <- dbReadTable(con, "Jobs")
skills_r <- dbReadTable(con, "Skills")
job_skills_r <- dbReadTable(con, "Job_Skills")

total_jobs <- nrow(jobs_r)

We will join the job-skill relationships with the skill names and exclude non-technical skills. Then, we will count the number of unique job postings requesting each skill, calculate their percentages, and select the 10 most frequently requested technical skills.

technical_skills <- job_skills_r %>%
  left_join(skills_r, by = "skill_id") %>%
  filter(!skill_name %in% non_technical_skills) %>%
  group_by(skill_name) %>%
  summarise(
    job_count = n_distinct(job_link),
    .groups = "drop"
  ) %>%
  mutate(
    percentage = round(100 * job_count / total_jobs, 2)
  ) %>%
  arrange(desc(job_count), skill_name) %>%
  slice_head(n = 10)

technical_skills %>%
  mutate(percentage = sprintf("%.2f", percentage))
# A tibble: 10 × 3
   skill_name         job_count percentage
   <chr>                  <int> <chr>     
 1 python                  4755 38.94     
 2 sql                     4520 37.01     
 3 data analysis           3227 26.42     
 4 machine learning        2632 21.55     
 5 data visualization      2267 18.56     
 6 aws                     1714 14.04     
 7 data engineering        1698 13.90     
 8 tableau                 1671 13.68     
 9 r                       1528 12.51     
10 data modeling           1498 12.27     

The results show that Python is the most frequently requested technical skill (38.94%), followed by SQL (37.01%), data analysis (26.42%), machine learning (21.55%), and data visualization (18.56%). Other frequently requested skills include AWS, data engineering, Tableau, R, and data modeling. Overall, these findings suggest that employers place strong emphasis on programming, data analysis, and data management capabilities.

We will use a horizontal bar chart to visualize the 10 most frequently requested technical skills. The percentages represent the proportion of job postings that mention each skill.

ggplot(
  technical_skills,
  aes(x = reorder(skill_name, percentage),
      y = percentage,
      fill = skill_name)
) +
  geom_col() +
  scale_fill_viridis_d(option = "turbo", guide = "none") +
  geom_text(
    aes(label = sprintf("%.2f%%", percentage)),
    hjust = -0.2,
    size = 3.5
  ) +
  coord_flip() +
  scale_y_continuous(
    limits = c(0, 45)
  ) +
  labs(
    title = "Top 10 Most Requested Technical Skills",
    x = "Technical Skill",
    y = "Job Postings (%)"
  ) +
  theme_minimal()

4.13.2 Technical Skill Demand by Job Role

We will classify job postings into three groups based on their job titles: Data Analyst, Data Scientist, and Data Engineer. Job titles containing these terms will be included regardless of experience level, such as Junior, Senior, or Lead. We will then count the number of job postings in each group.

role_counts <- jobs_r %>%
  mutate(
    job_role = case_when(
      grepl("data analyst", job_title, ignore.case = TRUE) ~ "Data Analyst",
      grepl("data scientist", job_title, ignore.case = TRUE) ~ "Data Scientist",
      grepl("data engineer", job_title, ignore.case = TRUE) ~ "Data Engineer",
      TRUE ~ NA_character_
    )
  ) %>%
  filter(!is.na(job_role)) %>%
  count(job_role, name = "job_count") %>%
  arrange(desc(job_count))

role_counts
        job_role job_count
1   Data Analyst      1888
2  Data Engineer      1751
3 Data Scientist       807

The results show that Data Analyst has the highest number of job postings (1,888), followed by Data Engineer (1,751) and Data Scientist (807). We will use these three groups to compare technical skill requirements across different job roles.

4.13.2.1 Top 10 Technical Skills for Data Analyst

We will identify the 10 most frequently requested technical skills in Data Analyst job postings. Skill demand will be measured as the percentage of Data Analyst postings mentioning each skill, including Junior and Senior positions.

data_analyst_jobs <- jobs_r %>%
  filter(grepl("data analyst", job_title, ignore.case = TRUE))

data_analyst_skills <- job_skills_r %>%
  semi_join(data_analyst_jobs, by = "job_link") %>%
  left_join(skills_r, by = "skill_id") %>%
  filter(!skill_name %in% non_technical_skills) %>%
  group_by(skill_name) %>%
  summarise(
    job_count = n_distinct(job_link),
    .groups = "drop"
  ) %>%
  mutate(
    percentage = round(
      100 * job_count / nrow(data_analyst_jobs), 2
    )
  ) %>%
  arrange(desc(job_count), skill_name) %>%
  slice_head(n = 10)

data_analyst_skills %>%
  mutate(percentage = sprintf("%.2f", percentage))
# A tibble: 10 × 3
   skill_name         job_count percentage
   <chr>                  <int> <chr>     
 1 data analysis           1307 69.23     
 2 sql                     1157 61.28     
 3 data visualization       977 51.75     
 4 python                   813 43.06     
 5 tableau                  731 38.72     
 6 power bi                 577 30.56     
 7 r                        562 29.77     
 8 excel                    519 27.49     
 9 data mining              366 19.39     
10 data management          363 19.23     

The results show that data analysis (69.23%) is the most frequently requested skill for Data Analyst positions, followed by SQL (61.28%) and data visualization (51.75%). Python (43.06%) and Tableau (38.72%) are also among the top five. Power BI, R, and Excel are frequently mentioned, highlighting the importance of data analysis, querying, and visualization skills for this role.

We will create a horizontal bar chart to visualize the 10 most frequently requested technical skills for Data Analyst positions. Each bar represents the percentage of Data Analyst job postings mentioning a particular skill.

ggplot(
  data_analyst_skills,
  aes(x = reorder(skill_name, percentage),
      y = percentage,
      fill = skill_name)
) +
  geom_col() +
  scale_fill_viridis_d(option = "turbo", guide = "none") +
  geom_text(
    aes(label = sprintf("%.2f%%", percentage)),
    hjust = -0.1,
    size = 3.5
  ) +
  coord_flip() +
  scale_y_continuous(
    limits = c(0, 80),
    expand = c(0, 0)
  ) +
  labs(
    title = "Top 10 Technical Skills for Data Analyst",
    x = "Technical Skill",
    y = "Data Analyst Job Postings (%)"
  ) +
  theme_minimal()

4.13.2.2 Top 10 Technical Skills for Data Scientist

We will identify the 10 most frequently requested technical skills in Data Scientist job postings. Skill demand will be calculated as the percentage of Data Scientist postings mentioning each skill, including Junior and Senior positions.

data_scientist_jobs <- jobs_r %>%
  filter(grepl("data scientist", job_title, ignore.case = TRUE))

data_scientist_skills <- job_skills_r %>%
  semi_join(data_scientist_jobs, by = "job_link") %>%
  left_join(skills_r, by = "skill_id") %>%
  filter(!skill_name %in% non_technical_skills) %>%
  group_by(skill_name) %>%
  summarise(
    job_count = n_distinct(job_link),
    .groups = "drop"
  ) %>%
  mutate(
    percentage = round(
      100 * job_count / nrow(data_scientist_jobs), 2
    )
  ) %>%
  arrange(desc(job_count), skill_name) %>%
  slice_head(n = 10)

data_scientist_skills %>%
  mutate(percentage = sprintf("%.2f", percentage))
# A tibble: 10 × 3
   skill_name         job_count percentage
   <chr>                  <int> <chr>     
 1 python                   675 83.44     
 2 machine learning         594 73.42     
 3 sql                      469 57.97     
 4 r                        394 48.70     
 5 data visualization       307 37.95     
 6 statistics               295 36.46     
 7 data analysis            255 31.52     
 8 data mining              180 22.25     
 9 spark                    179 22.13     
10 tableau                  175 21.63     

The results show that Python (83.44%) is the most frequently requested technical skill for Data Scientist positions, followed by machine learning (73.42%) and SQL (57.97%). R (48.70%) and data visualization (37.95%) complete the top five. Statistics (36.46%) is also frequently requested, highlighting the importance of programming, machine learning, and statistical analysis for this role.

We will create a horizontal bar chart to visualize the 10 most frequently requested technical skills for Data Scientist positions. Each bar represents the percentage of Data Scientist job postings mentioning a particular skill.

ggplot(
  data_scientist_skills,
  aes(x = reorder(skill_name, percentage),
      y = percentage,
      fill = skill_name)
) +
  geom_col() +
  scale_fill_viridis_d(option = "turbo", guide = "none") +
  geom_text(
    aes(label = sprintf("%.2f%%", percentage)),
    hjust = -0.1,
    size = 3.5
  ) +
  coord_flip() +
  scale_y_continuous(
    limits = c(0, 100),
    expand = c(0, 0)
  ) +
  labs(
    title = "Top 10 Technical Skills for Data Scientist",
    x = "Technical Skill",
    y = "Data Scientist Job Postings (%)"
  ) +
  theme_minimal()

4.13.2.3 Top 10 Technical Skills for Data Engineer

We will identify the 10 most frequently requested technical skills in Data Engineer job postings. Skill demand will be calculated as the percentage of Data Engineer postings mentioning each skill, including Junior and Senior positions.

data_engineer_jobs <- jobs_r %>%
  filter(grepl("data engineer", job_title, ignore.case = TRUE))

data_engineer_skills <- job_skills_r %>%
  semi_join(data_engineer_jobs, by = "job_link") %>%
  left_join(skills_r, by = "skill_id") %>%
  filter(!skill_name %in% non_technical_skills) %>%
  group_by(skill_name) %>%
  summarise(
    job_count = n_distinct(job_link),
    .groups = "drop"
  ) %>%
  mutate(
    percentage = round(
      100 * job_count / nrow(data_engineer_jobs), 2
    )
  ) %>%
  arrange(desc(job_count), skill_name) %>%
  slice_head(n = 10)

data_engineer_skills %>%
  mutate(percentage = sprintf("%.2f", percentage))
# A tibble: 10 × 3
   skill_name       job_count percentage
   <chr>                <int> <chr>     
 1 python                1288 73.47     
 2 sql                   1106 63.09     
 3 data engineering       905 51.63     
 4 spark                  715 40.79     
 5 java                   662 37.76     
 6 aws                    639 36.45     
 7 scala                  635 36.22     
 8 snowflake              621 35.42     
 9 data warehousing       558 31.83     
10 hadoop                 554 31.60     

The results show that Python (73.47%) is the most frequently requested technical skill for Data Engineer positions, followed by SQL (63.09%) and data engineering (51.63%). Spark (40.79%) and Java (37.76%) complete the top five. AWS, Scala, Snowflake, data warehousing, and Hadoop are also frequently requested, highlighting the importance of programming, cloud technologies, and large-scale data processing for this role.

We will create a horizontal bar chart to visualize the 10 most frequently requested technical skills for Data Engineer positions. Each bar represents the percentage of Data Engineer job postings mentioning a particular skill.

ggplot(
  data_engineer_skills,
  aes(x = reorder(skill_name, percentage),
      y = percentage,
      fill = skill_name)
) +
  geom_col() +
  scale_fill_viridis_d(option = "turbo", guide = "none") +
  geom_text(
    aes(label = sprintf("%.2f%%", percentage)),
    hjust = -0.1,
    size = 3.5
  ) +
  coord_flip() +
  scale_y_continuous(
    limits = c(0, 90),
    expand = c(0, 0)
  ) +
  labs(
    title = "Top 10 Technical Skills for Data Engineer",
    x = "Technical Skill",
    y = "Data Engineer Job Postings (%)"
  ) +
  theme_minimal()

4.13.3 Comparing Technical Skills Across Job Roles

The results show both similarities and differences in technical skill demand across Data Analyst, Data Scientist, and Data Engineer positions.

Python and SQL are among the most frequently requested skills across all three roles. Python is particularly common in Data Scientist (83.44%) and Data Engineer (73.47%) postings, while SQL is frequently requested in Data Analyst (61.28%), Data Scientist (57.97%), and Data Engineer (63.09%) positions.

Data Analyst positions place greater emphasis on data analysis (69.23%), SQL (61.28%), data visualization (51.75%), Python (43.06%), and Tableau (38.72%). These are the five most frequently requested technical skills for this role, highlighting the importance of analyzing, querying, and visualizing data.

Data Scientist positions show the highest demand for Python (83.44%), followed by machine learning (73.42%), SQL (57.97%), R (48.70%), and data visualization (37.95%). These findings highlight the importance of programming, machine learning, and data analysis for this role.

Data Engineer positions most frequently request Python (73.47%), SQL (63.09%), data engineering (51.63%), Spark (40.79%), and Java (37.76%). These findings reflect the importance of programming, data infrastructure, and large-scale data processing for this role.

Overall, the findings suggest that while all three roles share important technical skills, their priorities differ. Data Analysts focus more on analysis and visualization, Data Scientists on machine learning and statistical methods, and Data Engineers on data infrastructure and processing technologies.

5 Limitations

This analysis has several limitations. First, the dataset contains job postings from 2024, so the results may not reflect current employer requirements.

Second, skill names were standardized during data cleaning, but some variations may still remain. Additionally, the classification of technical and non-technical skills was based on a predefined exclusion list, which may not capture every non-technical skill.

Finally, job postings were grouped by job title, including different experience levels. Therefore, the analysis does not distinguish between Junior, Senior, and other experience levels within each role.

6 Conclusion

This project explored which technical data science skills are most valued by employers and how skill requirements differ across job roles.

The overall analysis showed that Python and SQL were the most frequently requested technical skills, followed by data analysis, machine learning, and data visualization.

The role-specific analysis revealed differences in employer requirements. Data Analyst positions emphasized data analysis, SQL, and visualization tools. Data Scientist positions focused more on Python, machine learning, and statistical skills, while Data Engineer positions placed greater emphasis on programming, data infrastructure, and large-scale data processing technologies.

Overall, the findings suggest that Python and SQL are valuable across multiple data-related careers, while other technical skill requirements depend on the specific job role. Understanding these differences can help job seekers prioritize the skills most relevant to their career goals.

7 Citation

We used ChatGPT (GPT-5.6 Luna), developed by OpenAI, to help us understand some of the functions we used in this project and to check our work.

OpenAI. (2026). ChatGPT (GPT-5.6 Luna) [Large language model]. [https://chatgpt.com/ Accessed October 22, 2026].