R, Веб-парсинг

R Project - веб-парсинг с помощью R - с использованием R и rvest

узнайте, как очистить данные из Интернета с помощью R и экспортировать их в формат CSV!

Отказ от ответственности: эта статья предназначена только для образовательных целей. Мы не призываем кого-либо очищать веб-сайты, особенно те веб-ресурсы, которые могут иметь положения и условия, запрещающие такие действия.

Информация везде. Информационный поток в Интернете непрерывен. Благодаря всем технологическим достижениям стало еще проще собирать, записывать, хранить и получать доступ к данным. Поэтому Интернет наполнен большим количеством наборов данных, которые вы можете использовать для своих личных проектов. Иногда вам может повезти, и вы получите доступ к полному, аккуратному и чистому набору данных. В других случаях вам не повезет, и у вас не будет доступа к полному набору данных. В таких случаях вам придется выполнять парсинг веб-страниц. В этой статье я расскажу вам, как очистить данные из Интернета с помощью языка программирования R.

В этой статье мы рассмотрим следующие вещи -

  1. Что такое парсинг?
  2. Методы парсинга
  3. Предварительные условия для очистки данных с помощью R
  4. Веб-парсинг с помощью R
  5. Анализ данных с помощью R

1. Что такое веб-парсинг?

Веб-парсинг или веб-сбор данных - это процесс сбора данных с разных веб-сайтов. Это метод, используемый для преобразования неструктурированных данных (формат HTML) в структурированный формат, к которому можно получить доступ и легко использовать. С помощью этого метода данные можно легко собирать и хранить в базах данных или электронных таблицах для дальнейшего анализа. В целом веб-сбор данных используется людьми, организациями и предприятиями по всему миру, которые хотят использовать большой объем общедоступных данных для принятия более эффективных и содержательных решений.

2. Методы парсинга веб-страниц

Существуют различные методы очистки данных из Интернета. Некоторые из способов -

  1. Соответствие текстовому шаблону
  2. Копипаст человека
  3. HTTP программирование
  4. Разбор HTML
  5. Парсинг DOM

3. Предварительные условия для очистки данных с помощью R

Чтобы начать работу с веб-парсингом, вы должны иметь практические знания языка программирования R. R очень легко изучить и использовать. В этой статье мы будем использовать пакет rvest, созданный знаменитым Хэдли Уикхемом. Сначала вам нужно установить этот пакет. Вы можете ознакомиться с документацией здесь. Также будут полезны дополнительные знания HTML и CSS. В Интернете доступно множество ресурсов для изучения обоих языков.

Поскольку HTML и CSS не являются основными навыками, необходимыми для того, чтобы стать специалистом по обработке данных, мы будем использовать здесь расширение Selector Gadget для очистки данных. Selector Gadget - это бесплатное расширение селектора CSS с открытым исходным кодом, доступное в интернет-магазине Google Chrome. Вы можете скачать и установить его здесь. Пожалуйста, убедитесь, что вы установили его, прежде чем запускать парсинг веб-страниц. Используя это расширение, вы можете выбрать любую часть веб-сайта и получить соответствующие теги, чтобы получить доступ к этой части, просто нажав на эту часть веб-сайта. Но если вы действительно хотите овладеть искусством парсинга веб-страниц, я рекомендую изучить HTML и CSS.

4. Веб-парсинг с помощью R

А теперь давайте приступим к поиску на веб-сайте IMDB лучших победителей конкурса фотографий 1927–2019 годов. Вы можете посетить сайт здесь.

# Lets fist install rvest package for parsing of HTML and / or XML files
install.packages("rvest")

Теперь давайте установим другие пакеты -

#lets install lubridate and tidyverse package for date and time manipulation
install.packages(c("lubridate", "tidyverse"))

Мы установили необходимые пакеты. Загрузим их.

# load all the necessary packages
library(tidyverse)
library(lubridate)
library(rvest)

После загрузки пакетов пора указать URL-адрес желаемого веб-сайта, который нужно очистить.

# now specify the url for desired website to be scraped
link <- "https://www.imdb.com/search/title/?count=100&groups=oscar_best_picture_winners&sort=year%2Cdesc&ref_=nv_ch_osc"
#Read the HTML code from the website
website <- read_html(link)
website
str(website)

Вы получите следующий результат -

{html_document}
<html xmlns:og="http://ogp.me/ns#" xmlns:fb="http://www.facebook.com/2008/fbml">
[1] <head>\n<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">\n<script type="text/javascript">var ue ...
[2] <body id="styleguide-v2" class="fixed">\n            <img height="1" width="1" style="display:none;visibility:hidd ...
> str(website)
List of 2
 $ node:<externalptr> 
 $ doc :<externalptr> 
 - attr(*, "class")= chr [1:2] "xml_document" "xml_node"

Мы очистим следующие данные с веб-сайта:

  1. Классифицировать
  2. Заголовок
  3. Описание
  4. Время выполнения
  5. Жанр
  6. Рейтинг
  7. Metascore
  8. Голоса
  9. Валовая выручка
  10. Актер
  11. Директор

Начнем с очистки поля «Ранги». Для этого воспользуемся расширением Selector Gadget. Используйте это расширение, чтобы указать селекторы CSS, которые включают раздел рейтинга веб-страницы. Просто нажмите на расширение в браузере и выберите курсором поле рейтинга. Это будет выглядеть примерно так -

Убедитесь, что все рейтинги выбраны правильно. Как только вы выберете все рейтинги, скопируйте соответствующий селектор CSS, указанный внизу. В данном случае это .text-primary.

Теперь мы знаем селектор CSS. Мы будем использовать этот простой код R, чтобы получить все рейтинги -

# using CSS selectors to scrape the rankings selection
rankings_html <- html_nodes(website, ".text-primary")
#converting the rankings data to the text format
ranks_data <- html_text(rankings_html)
#now lets have a look at our data
View(ranks_data)
#lets view some top elements
head(ranks_data)

После запуска этого кода вы получите следующий результат:

[1] "1." "2." "3." "4." "5." "6."

После получения данных убедитесь, что они имеют желаемый формат. Итак, давайте преобразуем наши данные в числовой формат -

# lets convert rankings data to numerical format
ranks_data <- as.numeric(ranks_data)
head(ranks_data)

Теперь наш результат будет выглядеть так -

[1] 1 2 3 4 5 6

Теперь мы очистим предыдущий выбор и выделим все заголовки.

# using CSS selectors to scrape the title selection
title_html <- html_nodes(website, ".lister-item-header a")
#converting the title data to the text format
title_data <- html_text(title_html)
#now lets have a look at our data
View(title_data)
# lets look at some top elements
head(title_data)

Результат будет выглядеть так -

[1] "Gisaengchung"                                    "Green Book"                                     
[3] "The Shape of Water"                              "Moonlight"                                      
[5] "Spotlight"                                       "Birdman or (The Unexpected Virtue of Ignorance)"

Теперь давайте сделаем это для данных описания -

# using CSS selectors to scrape the description selection
description_html <- html_nodes(website, ".ratings-bar+ .text-muted")
#converting the description data to the text format
description_data <- html_text(description_html)
# lets look at some top elements
head(description_data)

Вы получите результат как -

[1] "\n    Greed and class discrimination threaten the newly formed symbiotic relationship between the wealthy Park family and the destitute Kim clan."                                                  
[2] "\n    A working-class Italian-American bouncer becomes the driver of an African-American classical pianist on a tour of venues through the 1960s American South."                                   
[3] "\n    At a top secret research facility in the 1960s, a lonely janitor forms a unique relationship with an amphibious creature that is being held in captivity."                                    
[4] "\n    A young African-American man grapples with his identity and sexuality while experiencing the everyday struggles of childhood, adolescence, and burgeoning adulthood."                         
[5] "\n    The true story of how the Boston Globe uncovered the massive scandal of child molestation and cover-up within the local Catholic Archdiocese, shaking the entire Catholic Church to its core."
[6] "\n    A washed-up superhero actor attempts to revive his fading career by writing, directing, and starring in a Broadway production."

Теперь давайте очистим данные -

#lets process this data and remove "\n"
description_data <- gsub("\n", "", description_data)
#lets have another look at description data
head(description_data)

Вы получите результат как -

[1] "Greed and class discrimination threaten the newly formed symbiotic relationship between the wealthy Park family and the destitute Kim clan."                                                  
[2] "A working-class Italian-American bouncer becomes the driver of an African-American classical pianist on a tour of venues through the 1960s American South."                                   
[3] "At a top secret research facility in the 1960s, a lonely janitor forms a unique relationship with an amphibious creature that is being held in captivity."                                    
[4] "A young African-American man grapples with his identity and sexuality while experiencing the everyday struggles of childhood, adolescence, and burgeoning adulthood."                         
[5] "The true story of how the Boston Globe uncovered the massive scandal of child molestation and cover-up within the local Catholic Archdiocese, shaking the entire Catholic Church to its core."
[6] "A washed-up superhero actor attempts to revive his fading career by writing, directing, and starring in a Broadway production."

Давайте выполним этот код для выбора среды выполнения -

#using CSS selectors to scrape the movie run time section
runtime_html <- html_nodes(website, ".runtime")
#converting the run time data to text format
runtime_data <- html_text(runtime_html)
#lets look at some of the top elements
head(runtime_data)

Вы получите следующий результат -

[1] "132 min" "130 min" "123 min" "111 min" "129 min" "119 min"

Теперь давайте очистим данные -

#lets remove the min and convert it to numerical value
runtime_data <- gsub("min", "", runtime_data)
runtime_data <- as.numeric(runtime_data)
#lets have another look at the data
head(runtime_data)

Вы получите результат как -

[1] 132 130 123 111 129 119

А теперь давайте посмотрим на подборку жанров -

##Using CSS selectors to scrape the Movie genre section
genre_html <- html_nodes(website, ".genre")
#lets convert genre data to text format
genre_data <- html_text(genre_html)
#lets look at some of the top elements of the data
head(genre_data)

Вы получите результат как -

[1] "\nComedy, Drama, Thriller            "   "\nBiography, Comedy, Drama            " 
[3] "\nAdventure, Drama, Fantasy            " "\nDrama            "                    
[5] "\nBiography, Crime, Drama            "   "\nComedy, Drama

Теперь давайте очистим данные -

#lets process this data and remove "\n", excess spaces and keeping only first genre of the data
genre_data <- gsub("\n", "", genre_data)
genre_data<-gsub(" ","",genre_data)
genre_data<-gsub(",.*","",genre_data)
#Converting each genre from text to factor
genre_data <- as.factor(genre_data)
#lets have another look at the data
head(genre_data)

Вы получите результат как -

[1] Comedy    Biography Adventure Drama     Biography Comedy

Теперь давайте посмотрим на выбор рейтинга IMDB -

#Using CSS selectors to scrape the IMDB rating section
IMDB_ratings_html <- html_nodes(website, ".ratings-imdb-rating")
#lets convert html data to text data
IMDB_ratings_data <- html_text(IMDB_ratings_html)
#lets look at some of the top elements of the data
head(IMDB_ratings_data)

Вы получите результат как -

[1] "\n        \n        8.6\n    " "\n        \n        8.2\n    " "\n        \n        7.3\n    "
[4] "\n        \n        7.4\n    " "\n        \n        8.1\n    " "\n        \n        7.7\n    "

Теперь давайте очистим данные -

#lets convert the strings data to numerical data and convert it to numeric
IMDB_ratings_data <- gsub("\n", "", IMDB_ratings_data)
IMDB_ratings_data <- as.numeric(IMDB_ratings_data)
#lets look at some of the top elements of the data
head(IMDB_ratings_data)

Вы получите результат как -

[1] 8.6 8.2 7.3 7.4 8.1 7.7

Теперь давайте посмотрим на выбор голосов -

#Using CSS selectors to scrape the votes section
votes_html <- html_nodes(website, ".sort-num_votes-visible span:nth-child(2)")
#lets convert votes data to text data
votes_data <- html_text(votes_html)
#lets look at some of the top elements of the data
head(votes_data)

Вы получите результат как -

[1] "550,381" "376,701" "370,690" "273,379" "419,883" "579,887"

Теперь давайте очистим данные -

#lets remove the commas and convert votes to numerical
votes_data <- gsub(",", "", votes_data)
votes_data <- as.numeric(votes_data)
#lets look at some of the top elements of the data
head(votes_data)

Вы получите результат как -

[1] 550381 376701 370690 273379 419883 579887

А теперь давайте посмотрим на выбор директоров -

#Using CSS selectors to scrape the directors names
directors_html <- html_nodes(website, ".text-muted+ p a:nth-child(1)")
#lets convert directors data to text
directors_data <- html_text(directors_html)
#lets look a some of the top elements
head(directors_data)

Вы получите результат как -

[1] "Bong Joon Ho"          "Peter Farrelly"        "Guillermo del Toro"    "Barry Jenkins"        
[5] "Tom McCarthy"          "Alejandro G. Iñárritu"

Теперь давайте очистим данные -

#lets convert directors data into factors
directors_data <- as.factor(directors_data)
#lets look a some of the top elements
head(directors_data)

Вы получите результат как -

[1] Bong Joon Ho          Peter Farrelly        Guillermo del Toro    Barry Jenkins         Tom McCarthy         
[6] Alejandro G. Iñárritu

А теперь давайте посмотрим на выбор актеров -

#Using CSS selectors to scrape the actors section
actors_html <- html_nodes(website, ".lister-item-content .ghost+ a")
#lets convert actors data to text data
actors_data <- html_text(actors_html)
#lets look at some of the top elements
head(actors_data)

Вы получите результат как -

[1] "Kang-ho Song"    "Viggo Mortensen" "Sally Hawkins"   "Mahershala Ali"  "Mark Ruffalo"    "Michael Keaton"

Теперь давайте очистим данные -

#lets convert data to factors
actors_data <- as.factor(actors_data)
#lets look at some of the top elements again
head(actors_data)

Вы получите результат как -

[1] Kang-ho Song    Viggo Mortensen Sally Hawkins   Mahershala Ali  Mark Ruffalo    Michael Keaton

Теперь давайте посмотрим на выбор Metascore -

Я хочу, чтобы вы все внимательно изучили данные Metascore.

#Using CSS selectors to scrape the meta score section
metascore_html <- html_nodes(website, ".metascore")
#lets convert meta score data to text data
metascore_data <- html_text(metascore_html)
#lets look at some of the top elements
head(metascore_data)

Вы получите результат как -

[1] "96        " "69        " "87        " "99        " "93        " "87        "

Теперь давайте очистим данные -

#lets remove the extra space from the data and convert them as factors
metascore_data<-gsub(" ","",metascore_data)
metascore_data <- as.factor(metascore_data)
#lets look at some of the top elements
head(metascore_data)

Вы получите результат как -

[1] 96 69 87 99 93 87

А теперь посмотри внимательно -

# lets view the metascore data
View(metascore_data)
# after looking at the data, you will see that this section has only 76 rows wile we are scraping the data for 93 movies
#lets check the length of the meta score data
length(metascore_data)

Вы получите следующий результат -

[1] 76

Это произошло из-за того, что в некоторых фильмах нет соответствующих полей Metascore, показанных на скриншоте ниже - Итак, мы добавим NA в остальные записи и продолжим -

Вот как вы должны это делать -

# from the output we can see that the length of metascore data is only 76 while we are scarping data for 93 movies.
#This is because some of the movies do not have the metascore fields as shown in the screen shot below.
# so lets put NAs into the rest of the field.
for (i in c(64,65,68,71,73,75,76,77,79,82,83,84,87,88,91,92,93)){
  a <- metascore_data[1:(i-1)]
  b <- metascore_data[i:length(metascore_data)]
  metascore_data <- append(a, list("NA"))
  metascore_data <- append(metascore_data, b)
}
metascore_data <- as.numeric(unlist(metascore_data))
# lets check the length again
length(metascore_data)
# we can see that it has 94 entries now. so lets remove extra one
metascore_data <- metascore_data[-94]
#lets check the length again
length(metascore_data)

После выполнения этого шага вы получите на выходе 93 записи.

#lets look at some of the top elements again
head(metascore_data)
# lets look at the summary statistics
summary(metascore_data)

Вы получите следующий результат -

[1] 31  7 22 34 28 22
Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
   1.00   12.75   20.00   18.62   25.25   34.00      17

Теперь давайте посмотрим на выборку валового дохода -

То же самое происходит с данными о валовом доходе.

#Using CSS selectors to scrape the gross revenue section
gross_revenue_html <- html_nodes(website, ".ghost~ .text-muted+ span")
# lets convert gross revenue data to text data
gross_revenue_data <- html_text(gross_revenue_html)
# lets look at some of the top elements
head(gross_revenue_data)

Вы получите следующий результат -

[1] "$53.37M" "$85.08M" "$63.86M" "$27.85M" "$45.06M" "$42.34M"

Теперь давайте очистим данные -

# lets remove the $ and M signs
gross_revenue_data <- gsub("M", "", gross_revenue_data)
gross_revenue_data <- substring(gross_revenue_data, 2,6)
head(gross_revenue_data)

Давайте еще раз очистим данные -

# lets check the length of the data
length(gross_revenue_data)
#after checking the lenght of the data, you will notice the same thing which happened with metascore data.
#so lets put NAs into all the missing values
# lets fill missing values with NA
for (i in c(62,65,71,72,73,79,83,84,85,89)){
  s <- gross_revenue_data[1:(i-1)]
  d <- gross_revenue_data[i:length(gross_revenue_data)]
  gross_revenue_data <- append(s, list("NA"))
  gross_revenue_data <- append(gross_revenue_data, d)
}
#lets convert gross data to numerical
gross_revenue_data <- as.numeric(gross_revenue_data)
head(gross_revenue_data)
#Let's have another look at the length of gross revenue data
length(gross_revenue_data)
summary(gross_revenue_data)

Вы получите следующий результат -

[1] 93
Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
   0.01   24.02   54.58   81.70  110.20  659.30      10

Мы успешно скопировали все 11 элементов из 93 фильмов-победителей с 1927 по 2019 год. Итак, теперь давайте создадим фрейм данных, объединяющий вышеупомянутые функции.

# combining all the lists to form the new data frame
best_movies_data <- data.frame(Rank = ranks_data, Title = title_data, Description = description_data, Runtime = runtime_data, Genre = genre_data, IMDB_Ratings = IMDB_ratings_data, Votes = votes_data, Directors = directors_data, Actors = actors_data, Metascore = metascore_data, Gross_Revenue = gross_revenue_data)
View(best_movies_data)
# lets look at structure of the data frame
str(best_movies_data)

Вы получите следующий результат -

'data.frame': 93 obs. of  11 variables:
 $ Rank         : num  1 2 3 4 5 6 7 8 9 10 ...
 $ Title        : chr  "Gisaengchung" "Green Book" "The Shape of Water" "Moonlight" ...
 $ Description  : chr  "     Greed and class discrimination threaten the newly formed symbiotic relationship between the wealthy Park f"| __truncated__ "     A working-class Italian-American bouncer becomes the driver of an African-American classical pianist on a "| __truncated__ "     At a top secret research facility in the 1960s, a lonely janitor forms a unique relationship with an amphi"| __truncated__ "     A young African-American man grapples with his identity and sexuality while experiencing the everyday stru"| __truncated__ ...
 $ Runtime      : num  132 130 123 111 129 119 134 120 100 118 ...
 $ Genre        : Factor w/ 6 levels "Action","Adventure",..: 4 3 2 6 3 4 3 3 4 3 ...
 $ IMDB_Ratings : num  8.6 8.2 7.3 7.4 8.1 7.7 8.1 7.7 7.9 8 ...
 $ Votes        : num  550381 376701 370690 273379 419883 ...
 $ Directors    : Factor w/ 81 levels "Alejandro G. Iñárritu",..: 9 55 28 4 72 1 68 6 50 71 ...
 $ Actors       : Factor w/ 86 levels "Al Pacino","Albert Finney",..: 48 82 76 56 58 62 15 4 43 19 ...
 $ Metascore    : num  31 7 22 34 28 22 31 21 24 23 ...
 $ Gross_Revenue: num  53.4 85.1 63.9 27.9 45.1 ...

Теперь самый важный шаг. Давайте экспортируем наш фрейм данных из R в формат CSV. Сделаем это следующим образом -

# lets export this data to a csv file
write.csv(best_movies_data, "C:\\Users\\Kunal Kulkarni\\Desktop\\R data\\bestmoviesdata.csv", row.names = FALSE)

Давайте проверим, каково среднее время выполнения -

# average runtime
print(avg <- mean(best_movies_data$Runtime))

Вы получите следующий результат -

[1] 137.6129

Давайте проверим сводку -

summary(best_movies_data)

Вы получите следующий результат -

Rank       Title           Description           Runtime            Genre     IMDB_Ratings       Votes        
 Min.   : 1   Length:93          Length:93          Min.   : 90.0   Action   : 3   Min.   :5.700   Min.   :   4584  
 1st Qu.:24   Class :character   Class :character   1st Qu.:118.0   Adventure: 8   1st Qu.:7.500   1st Qu.:  34774  
 Median :47   Mode  :character   Mode  :character   Median :129.0   Biography:18   Median :7.900   Median : 178591  
 Mean   :47                                         Mean   :137.6   Comedy   :13   Mean   :7.788   Mean   : 343034  
 3rd Qu.:70                                         3rd Qu.:155.0   Crime    : 9   3rd Qu.:8.100   3rd Qu.: 472817  
 Max.   :93                                         Max.   :238.0   Drama    :42   Max.   :9.200   Max.   :1807653  
                                                                                                                    
                Directors                Actors     Metascore     Gross_Revenue   
 William Wyler       : 3   Dustin Hoffman   : 3   Min.   : 1.00   Min.   :  0.01  
 Billy Wilder        : 2   Clark Gable      : 2   1st Qu.:12.75   1st Qu.: 24.02  
 Clint Eastwood      : 2   Laurence Olivier : 2   Median :20.00   Median : 54.58  
 David Lean          : 2   Leonardo DiCaprio: 2   Mean   :18.62   Mean   : 81.70  
 Elia Kazan          : 2   Marlon Brando    : 2   3rd Qu.:25.25   3rd Qu.:110.20  
 Francis Ford Coppola: 2   Russell Crowe    : 2   Max.   :34.00   Max.   :659.30  
 (Other)             :80   (Other)          :80   NA's   :17      NA's   :10

Давайте проанализируем извлеченные из Интернета данные -

Когда у вас есть нужные данные, вы можете выполнять различные задачи, такие как анализ данных, очистка данных, визуализация данных и извлечение из них важной информации. Итак, давайте проанализируем данные, которые мы только что скопировали, и сделаем из них что-то значимое, визуализировав эти данные.

Начнем с установки и загрузки пакета ggplot2. Пакет ggplot2 является частью семейства пакетов Tidyverse, и это очень мощный пакет, созданный для визуализации данных. Его часто считают грамматикой графики. Подробнее о пакете ggplot2 читайте здесь. Также вы можете использовать шпаргалку по ggplot2 для справки. Его можно бесплатно загрузить в RStudio или вы можете скачать здесь. Итак, приступим -

# The easiest way to install ggplot2 package is to install the whole tidyverse package:
install.packages("tidyverse")
# Alternatively, you can just install just ggplot2 package from CRAN :
install.packages("ggplot2")
# load the library
library(tidyverse)
#or
library(ggplot2)

Основываясь на данных, которые мы только что соскребли, давайте посмотрим, какой фильм из какого жанра имеет самое продолжительное время просмотра -

# now lets analyze our data
#lets check which genre has the the longest runtime by plotting a graph of it
best_movies_data %>% ggplot(aes(Runtime, IMDB_Ratings, color = Genre, size = Votes, bins = 50)) + geom_point(aes(size = IMDB_Ratings)) + xlab("Runtime in minutes") + ylab("Ratings") + ggtitle("Genre with the longest runtime")

Вы получите следующий результат -

Давайте посмотрим, какой фильм заработал больше всего -

best_movies_data %>% ggplot(aes(Runtime, Gross_Revenue, color = Genre)) + geom_point() + ggtitle("Movie with the highest earning")

Вы получите следующий результат -

Основываясь на данных, которые мы только что проанализировали, давайте посмотрим, какой жанр приносит наибольший доход в миллионах -

# lets plot the graph of gross revenue and genre
best_movies_data %>% ggplot(aes(Genre, Gross_Revenue, color = Genre)) + geom_line() + ggtitle("Genre with the highest earnings")

Вы получите следующий результат -

Заключение

Я надеюсь, что эта статья даст вам базовый обзор того, как очистить данные из Интернета с помощью R и как их анализировать. Поскольку большинство данных, доступных в Интернете, представлены в неструктурированном формате, парсинг веб-страниц является действительно важным и обязательным навыком для любого специалиста по данным. Если у вас есть какие-либо вопросы по этой статье, дайте мне знать в разделе комментариев ниже.

Благодарю вас!