Анализ лихорадки денге с помощью Tweepy, TextBlob, NewsAPI и Geopandas

Введение

Информация - сила. Информация на кончиках ваших пальцев - это суперсила.

За это время я случайно наткнулся на множество визуализаций COVID-19 на Reddit, Twitter и Facebook. Многие из них очень вдохновляют, и очень приятно иметь возможность усвоить огромное количество информации за такой короткий промежуток времени.

У меня была мотивация создать информационную панель по COVID-19, но я чувствовал, что их более чем достаточно. Поэтому я решил заняться другой проблемой - лихорадкой денге, которая также является насущной. Данные легко доступны на data.gov.sg, но есть 3 основные проблемы:

  1. Некоторые данные не имеют визуализации
  2. Данные повсюду, и требуются дополнительные усилия для фильтрации результатов.
  3. Недостаточно данных, чтобы рассказать историю

Это побудило меня создать приборную панель самостоятельно. Этот проект состоит из 3 основных частей:

  1. Анализ. Я визуализировал количество случаев денге и создал наложенную карту горячих зон денге и мест размножения.
  2. Получение новостей. На этой вкладке можно получать новости из нескольких источников по теме «Денге».
  3. Анализ Twitter: эта функция составляет основную часть проекта. Короче говоря, я получаю данные из твиттера в режиме реального времени и анализирую их мнения, представляя их в виде диаграмм.

Цель этого проекта - показать, насколько просто можно создать такое мощное приложение. Это не обязательно должно быть на Денге. Это может быть по любой теме. Для этого проекта важен не контент, а навыки. Это скорее подтверждение концепции, чем конечный продукт. Я проведу вас по всем 3 различным частям проекта, предоставив фрагменты кода. Надеюсь, вы сможете воспроизвести это в своих собственных значимых проектах. Прокрутите статью до конца, чтобы увидеть ссылку на полный код.

Концепции

Прежде чем начать, я хотел бы рассказать о том, как строится этот проект.

Этот проект построен на Dash, фреймворке Python для создания проектов Data Science / ML. Наряду с компонентом начальной загрузки dash (DBC) и основными компонентами dash (DCC) вам потребуются минимальные навыки HTML / CSS для создайте свое веб-приложение.

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

Часть 0: Dash

Приложения Dash в основном состоят из 2-х разделов.

Раздел 1. Макет приложения

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

# Code partially hidden for readability
# Layout of entire app
app.layout = html.Div(
    [
        navbar,
        dbc.Tabs(
            [
                dbc.Tab(analysisTab, id="label_tab1", label="Visuals"),
                dbc.Tab(newsTab, ...),
                dbc.Tab(socialMediaTab, ...),
                dbc.Tab(infoTab, ...),
            ],
            style={"font-size": 20, "background-color": "#b9d9eb"},
        ),
    ]
)

Конечно, вам нужно будет указать, что такое analysisTab, newsTab и т. Д. Вы можете увидеть, насколько абстрактным становится ваше приложение с DBC и DCC. И DBC, и DCC предоставляют кнопки, вкладки, формы, панель навигации и многие другие важные компоненты.

Раздел 2. Обратные вызовы приложений (необязательно)

Обратные вызовы приложения можно рассматривать как внутреннюю часть вашего приложения. Если вы наблюдаете за приведенным выше кодом, вы заметите, что я присвоил id="label_tab1" своему analysisTab, это помогает нам определить, какие обратные вызовы использовать для этого конкретного компонента.

Пример обратного вызова показан на рисунке:

@app.callback(
    Output("sa-graph", "children"), [Input("interval-component-slow", "n_intervals")]
)
def sa_line(n):
    children = [...]
    return children

При этом он принимает входной компонент interval-component-slow с указанным параметром n_intervals и выводит результаты children, созданные методом sa_line(), в выходной компонент sa-graph. Это особенно полезно, если вы хотите сделать свое приложение интерактивным. Например, вы можете разрешить изменение визуализации в соответствии с переключателем, который был нажат пользователем.

Однако обратный вызов не всегда требуется, если вы хотите, чтобы ваше приложение было статическим. Вы поймете, что мои первые 2 вкладки не использовали обратные вызовы, так как я не требую никаких данных от моих пользователей. От меня требуется только пересылать им информацию. Третья вкладка (анализ твиттера) - особый случай. Мне пришлось использовать обратные вызовы, так как я хочу, чтобы мой анализ обновлялся каждые 30 секунд. Подробнее об этом позже!

Часть 1: Визуализация

Полный код здесь.

Основным обучающим моментом в этом разделе является использование Геопанд и Фолиума.

Геопанды

Данные, загруженные с data.gov.sg, имеют формат GeoJSON. Файлы GeoJSON позволяют кодировать географические данные в объект. Он имеет очень структурированный формат, который включает тип, геометрию и свойства местоположения. Поскольку кластеры денге включают участок земли, наши местоположения кодируются в объекте Polygon, который представляет собой массив долгот и широт.

Читать файл GeoJSON легко. Мы просто используем Геопанды:

central_data = gpd.read_file("data/dengue-cases-central-geojson.geojson")

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

Фолиум

Folium позволяет нам визуализировать наши данные GeoJSON в виде листовок (библиотеки JavaScript для интерактивных карт).

Сначала мы создаем карту и приближаемся к желаемому месту (в моем случае я выбрал долготу и широту Сингапура):

kw = {"location": [1.3521, 103.8198], "zoom_start": 12}
m = folium.Map(**kw)

Затем я добавляю на карту 2 слоя (один для кластеров, а другой для зон размножения):

folium.GeoJson(<FIRST LAYER DATA>, ...).add_to(m)
folium.GeoJson(<SECOND LAYER DATA>, ...).add_to(m)

Наконец, поскольку Dash не позволяет мне отображать объекты фолио напрямую, я экспортировал полученную карту в файл HTML и отобразил его с помощью IFrame:

m.save("dengue-cluster.html")
html.Iframe(id="dengue-map", srcDoc=open("dengue-cluster.html", "r").read(), ...)

Часть 2: Редактор новостей

Полный код здесь.

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

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

Хранение ключей (локальная система)

Ключи очень важны! Так же, как вы не делитесь своими ключами от дома с друзьями, вы не захотите делиться своими ключами API. Когда вы разрабатываете в своей локальной системе, я настоятельно рекомендую вам сохранить все свои ключи в файле keys.py и добавить этот файл в .gitignore, чтобы он не передавался в оперативный режим.

Хранение ключей (Heroku)

Поскольку вы не отправляли keys.py на GitHub, Heroku не может получить к нему доступ. Вместо этого у Heroku есть свои собственные переменные конфигурации. Вы можете добавить свои ключи в Heroku либо через интерфейс командной строки, либо через их графический интерфейс.

Код!

В целях развертывания мы получаем наш код для получения ключей в зависимости от того, находимся ли мы в нашей локальной системе или Heroku:

try:
    from keys import newsapikey  # retrieve from local system
    newsapi = NewsApiClient(api_key=newsapikey)
except:
    newsapikey = os.environ["newapi_key"]  # retrieve from Heroku
    newsapi = NewsApiClient(api_key=newsapikey)

Затем мы можем, наконец, получить наши новостные статьи:

all_articles = newsapi.get_everything(
        q="dengue singapore",
        from_param=date_n_days_ago,
        to=date_now,
        language="en",
        sort_by="publishedAt",
        page_size=100,
    )

Есть 3 основные функции newsapi.get_everything(), newsapi.get_top_headlines() и newsapi.get_sources(). Используйте все, что сочтете нужным! Затем мы можем получить наш контент. Пример получения заголовка всех статей показан ниже:

all_articles_title = [
        str(all_articles["articles"][i]["title"])
        for i in range(len(all_articles["articles"]))
    ]

Теперь, когда у вас есть данные, вы можете добавить их с помощью DBC. Для этого проекта я использовал CardImg, CardBody и CardFooter.

Часть 3: Анализ настроений в Twitter в режиме реального времени

Это составляет основную часть проекта. Я использовал Tweepy, библиотеку Python, которая позволяет нам получить доступ к Twitter API для потоковой передачи твитов. Моя цель - получить все твиты, связанные с лихорадкой денге, предварительно обработать текст, проанализировать настроения и визуализировать результаты. Твиты постоянно собираются из Twitter, и мое приложение будет обновляться каждые 30 секунд для отображения новых данных.

Рабочий процесс

На этом этапе мне, вероятно, следует познакомить вас с рабочим процессом.

  1. Твиты извлекаются из Twitter
  2. Твиты очищаются (переводятся на английский, смайлы удаляются, ссылки удаляются), тональность твитов анализируется TextBlob)
  3. Устанавливается соединение с базой данных, и обработанные твиты и мнения помещаются в базу данных каждый раз, когда поступает новый твит. Для локальной системы я использовал MySQL, а для Heroku - их базу данных PostgreSQL. Этот скрипт продолжает работать.
  4. В то же время я извлекаю твиты старше суток и удаляю их из базы данных. Это гарантирует, что в нашей базе данных не закончится место.
  5. Когда мое приложение работает параллельно, оно будет извлекать из базы данных данные за час каждые 30 секунд и отображать их.

ключи Ключи КЛЮЧИ

Как и NewsAPI, Tweepy требует, чтобы мы также сгенерировали ключи API. Зайдите в Twitter Developer, чтобы получить свое!

Скрапинг Twitter

Полный код здесь. Обратите внимание, что закомментированные коды предназначены для MySQL, а закомментированные коды - для PostgreSQL.

Шаг 1. Инициализируйте базу данных, создав для нас базу данных, если база данных и таблица не существуют.

DATABASE_URL = os.environ["DATABASE_URL"]
conn = psycopg2.connect(DATABASE_URL, sslmode="require")
cur = conn.cursor()
cur.execute(
    """
        SELECT COUNT(*)
        FROM information_schema.tables
        WHERE table_name = '{0}'
        """.format(
        parameters.TABLE_NAME
    )
)
if cur.fetchone()[0] == 0:
    cur.execute(
        "CREATE TABLE {} ({});".format(
            parameters.TABLE_NAME, parameters.TABLE_ATTRIBUTES
        )
    )
    conn.commit()
cur.close()

Шаг 2. Инициализируйте Twitter API и начните потоковую передачу.

myStreamListener = MyStreamListener()
myStream = tweepy.Stream(auth=api.auth, listener=myStreamListener)
myStream.filter(track=parameters.TRACK_WORDS)

Шаг 3. Обработка каждого поступающего твита. Для этого вам нужно переопределить tweepy.StreamListener, который является классом, отвечающим за отправку вам новых твитов из Твиттера.

# Override tweepy.StreamListener to add logic to on_status
class MyStreamListener(tweepy.StreamListener):
    def on_status(self, status):
        # Start modifying
        id_str = status.id_str
        created_at = status.created_at
        clean_text, text = preprocess(status.text)
        sentiment = TextBlob(text).sentiment
        polarity = sentiment.polarity
        ...
        # SQL statement for pushing tweets to DB
        ...
        # SQL statement for deleting and cleaning DB
        ...

Сделать это живым

Уловка для обновления вашего приложения каждые x секунд заключается в dcc.Interval. Добавляя интервал к вашему приложению в качестве входных данных для вашего обратного вызова, вы сообщаете своему приложению, что нужно регенерировать ваш компонент каждые x секунд.

dcc.Interval(
            id="interval-component-slow",
            interval=30000,  # in milliseconds
            n_intervals=0,
        ),

Линейный график + круговая диаграмма настроений

Полный код для визуализаций здесь.

Во-первых, мы должны получить данные, которые были очищены:

# Loading data from Heroku PostgreSQL
    DATABASE_URL = os.environ["DATABASE_URL"]
    db_connection = psycopg2.connect(DATABASE_URL, sslmode="require")
# Load last 1 hour data from MySQL
    ...
query = "SELECT ... FROM {} WHERE created_at >= '{}' ".format(
        "dengue", timenow
    )
df = pd.read_sql(query, con=db_connection)
# Convert UTC into SGT
    ...

Затем данные очищаются и преобразуются для построения временного ряда:

# Clean and transform data to enable time series
    # Bin into 5 minutes
    result = (
        df.groupby([pd.Grouper(key="created_at", freq="5min"), "polarity"])
        .count()
        .reset_index()
    )
    ...

Наконец, с результатами, мы их визуализируем с помощью Объектов графического представления. Линейный график в левом верхнем углу создается с помощью go.Scatter, а круговая диаграмма в правом верхнем углу создается с помощью go.Pie. Вот как легко создавать визуальные эффекты!

Получение реальных твитов

Твиты отображаются с помощью Dash DataTable.

dash_table.DataTable(id='table', columns=[{"name": i, "id": i} for i in df.columns], data=df.to_dict('records'))

Облако слов реальных твитов

В настоящее время не так много поддержки словесных облаков. Обычный метод - создать изображение и добавить его к нашим визуализациям. Нетрадиционный метод, который я использовал, отображает слова по осям X и Y, и вместо отображения координат я отображаю текст. Следовательно, это облако слов создано с использованием go.Scatter.

go.Scatter(x=wc_data.x,
           y=wc_data.y,
           mode="text",
           text=wc_data.words,
           ...)

Часть 4: Развертывание на Heroku

Теперь, когда ваше приложение хорошо работает в вашей локальной системе, пришло время развернуть его в Heroku. Я настоятельно рекомендую вам ознакомиться с этим 15-минутным руководством о том, как это сделать. В противном случае см. Сводку ниже:

  1. Создайте учетную запись Heroku и загрузите Heroku CLI
  2. Убедитесь, что все зависимости для вашего проекта загружены. Кроме того, запустите pip install gunicorn
  3. Создайте Procfile, который скажет Heroku, что запускать.
  4. Создайте файл requirements.txt с pip freeze > requirements.txt. Это важно для Heroku, чтобы понять, какие пакеты вы используете, чтобы они также могли его загрузить.
  5. Разверните свое приложение с помощью
heroku login
heroku create "<YOUR APP NAME>"
git add .
git commit -m "<ANY MESSAGE>"
git push heroku master
heroku ps:scale web=1

Примечание. Для одновременной работы парсера твитов и приложения вам потребуются 2 разных приложения. В противном случае вы можете заплатить за дополнительные стенды, которые являются контейнерами Heroku. Ваш Procfile будет другим. Чтобы использовать ту же базу данных, установите DATABASE_URL (переменные конфигурации) вашего парсера таким же, как DATABASE_URL вашего приложения.

Также обратите внимание, что загрузка приложения займет некоторое время. Это потому, что я использую бесплатные дино. Бесплатные дино будут переведены в спящий режим, если к приложению не будет доступа в течение установленного времени.

Вывод

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

В этой статье я надеюсь, что вы получили более глубокое понимание Dash, Tweepy, NewsAPI, Geopandas и Folium. Я очень умоляю вас создавать похожие приложения для разных контекстов.

Ссылки:

  1. Https://dengue-db.herokuapp.com/
  2. Https://github.com/bensjx/Dengue-dashboard
  3. Https://github.com/bensjx/twitter-scrap-dengue