«Большинству из нас нужно послушать музыку, чтобы понять, насколько она прекрасна. Но часто мы представляем статистику так: мы просто показываем ноты, мы не играем музыку ». - Ганс Рослинг

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

Недавний проект, который я завершил для Enigma Public, был посвящен гендерному разрыву в заработной плате с использованием данных о доходах из исследования американского сообщества. Я хотел показать разницу в заработной плате мужчин и женщин в самых распространенных профессиях в США. Визуализация - отличный способ помочь аудитории осмыслить отношения между двумя точками данных. Связанный точечный график с его минималистским стилем и четкой читаемостью казался лучшей диаграммой для представления информации. Если вы хотите узнать больше об изучении социальных проблем с помощью общедоступных данных, см. Мой предыдущий пост в блоге здесь.

Я использовал библиотеку D3 для создания точечной диаграммы. Хотя существует множество библиотек для построения диаграмм JavaScript, D3 широко считается золотым стандартом для визуализации данных в JavaScript, позволяя максимально настраивать и контролировать конечный продукт. Хотя это может пугать, а синтаксис сбивать с толку, знание нескольких основных концепций делает библиотеку намного более доступной. В приведенном ниже руководстве рассказывается, как создать связную точечную диаграмму в D3, а также основные принципы построения диаграмм D3. Вот что мы собираемся сделать:

Вот кодовый код с завершенной визуализацией, чтобы вы могли увидеть готовый код и следовать по нему. Учебник предполагает базовое понимание JavaScript, включая ES6 и промисы.

Эта диаграмма основана на графике соединенных точек Кейла Тилфорда, который можно найти здесь: https: //bl.ocks.org/tlfrd/e1dd ....

Получение данных

Сначала нам нужно получить данные в наш проект из Enigma Public. Мы можем использовать API, который обеспечивает программный доступ ко всем данным Enigma Public. Чтобы узнать больше об API и о том, как интегрировать данные Enigma в ваши проекты разработки, просмотрите документацию здесь.

Мы будем использовать Fetch API, интерфейс на основе обещаний для получения ресурсов в Интернете. Есть два способа получить набор данных:

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

Первый вариант полезен, если набор данных большой или вам нужен только небольшой конкретный объем данных. В приведенной выше таблице мы ищем выбранные профессии по оси ординат. Поэтому мы сформулируем запрос, который возвращает только строки с указанным нами именем столбца (Occupational_Category). См. Документацию по API Enigma Public, чтобы помочь сформулировать поисковые запросы. Поскольку вы должны использовать символы пробела в кодировке URL (% 20) в строке, заключенной в кавычки, мы будем использовать encodeURIComponent() для кодирования названий профессий, а затем интерполировать их в запрос на выборку.

const occupations = encodeURIComponent('"Driver/sales workers and truck drivers" OR "Elementary and middle school teachers" OR "Registered nurses" OR "Retail salespersons" OR "Accountants and auditors" OR "Janitors and building cleaners" OR "Sales representatives, wholesale and manufacturing" OR "Construction laborers" OR "Cashiers" OR "Customer service representatives"')

fetch(`https://public.enigma.com/api/snapshots/3ca9486a-db7c-4216-8e99-0428e3b0b54d?query_mode=advanced&query=Occupational_Category:(${occupations})&row_limit=10`)
    .then((resp) => resp.json()) 
    .then((data) => {
      return data.table_rows.rows;
     })
     .then(mapData).then(drawSVG)
     .catch((error) => console.log(error));

Для второго варианта мы можем запросить весь набор данных, а затем отфильтровать нужные поля. Обратите внимание, что в цепочке обещаний есть дополнительная функция filterData(). Поскольку набор данных составляет 560 строк, нам нужно установить row_limit достаточно высоким, чтобы вернуть все данные. Здесь установлено значение 600, но вы можете запросить до 10 000 строк.

fetch('https://public.enigma.com/api/snapshots/3ca9486a-db7c-4216-8e99-0428e3b0b54d?&row_limit=600')
  .then((resp) => resp.json()) 
  .then((data) => {
    return data.table_rows.rows;
   })
  .then(filterData).then(mapData).then(drawSVG)
  .catch((error) => console.log(error));

Теперь, когда у нас есть все данные, мы можем фильтровать только те строки, которые нам нужны, помещая названия занятий в массив и выполняя фильтрацию для этого массива.

function filterData(data){
    const selectedFields = [
        "Driver/sales workers and truck drivers", 
        "Elementary and middle school teachers", 
        "Registered nurses", 
        "Retail salespersons", 
        "Accountants and auditors", 
        "Janitors and building cleaners", 
        "Sales representatives, wholesale and manufacturing", 
        "Construction laborers", 
        "Cashiers", 
        "Customer service representatives"
    ]
    return data.filter((fieldName)=>{
         return selectedFields.includes(fieldName[0])
    });
};

Форматирование данных

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

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

function mapData(result){
    const formattedData = [];
    result.forEach((val)=>{
            let mappedData = {}
            mappedData["name"] = val[0]
            mappedData["max"] = val[11]
            mappedData["min"] = val[13]
            formattedData.push(mappedData)
    });
    return formattedData.sort(function(a, b) {
          if(parseInt(a["max"]) < parseInt(b["max"])) return 1 * 1;
          if(parseInt(a["max"]) > parseInt(b["max"])) return -1 * 1;
              return 0;
    });
};

Отформатированные данные теперь выглядят так:

data = [
 {"name":"Accountants and auditors","max":"76129","min":"57370"},
 {"name":"Registered nurses","max":"70952","min":"64413"},
 {"name":"Sales representatives, wholesale and manufacturing","max":"70464","min":"54077"},
 {"name":"Elementary and middle school teachers","max":"53096","min":"50021"},
 {"name":"Driver/sales workers and truck drivers","max":"42435","min":"32237"},
 {"name":"Retail salespersons","max":"40116","min":"26781"},
 {"name":"Customer service representatives","max":"36744","min":"32095"},
 {"name":"Construction laborers","max":"32214","min":"30378"},
 {"name":"Janitors and building cleaners","max":"30654","min":"22962"},
 {"name":"Cashiers","max":"22413","min":"20482"}
];

Построение диаграммы

Теперь, когда наши данные имеют правильный формат, мы можем приступить к построению диаграммы. Для целей этой программы весь код D3 заключен в функцию drawSVG().

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

1. Сначала давайте создадим контейнер div в html, куда мы добавим визуализацию:

<div id="container"></div>

2. Затем мы установим поля (оставляя слева широкое поле для названий занятий), ширину и высоту; и создайте SVG и добавьте его в #container.

function drawSVG(data) {
    let margin = {top: 90, right: 15, bottom: 50, left: 350},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;
    let svg = d3.select("#container").append("svg")
      .attr("width", width + margin.left + margin.right)
      .attr("height", height + margin.top + margin.bottom)
      .append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top 
")");

3. Определите масштабы и пути линий для наших данных. Этот синтаксис специфичен для D3 и может выглядеть пугающе! Но мы рассмотрим это ниже.

let y = d3.scaleBand()
  .range([0, height])
  .paddingInner(0.5)
  .paddingOuter(0.7);

let x = d3.scaleLinear()
  .range([0, width]);

let lineGenerator = d3.line();

let axisLinePath = function(d) {
  return lineGenerator([[x(d) + 0.5, 0], [x(d) + 0.5, height]]);
};

let lollipopLinePath = function(d) {
  return lineGenerator([[x(d.min), 0], [x(d.max), 0]]);
};

-Два важных понятия в D3 - это домен и диапазон.

  • Домен в контексте D3 относится к вашим данным и границам, в которых они находятся. Если мои данные представляют собой массив чисел не меньше 1 и не больше 10 000, мой домен будет от 1 до 10 000.
  • Диапазон относится к сопоставлению между входом домена и выходом (диапазоном). Например, если у вас есть точки данных от 1 до 10 000, у вас, скорее всего, не будет диаграммы с шириной 10 000 пикселей. Вам нужно будет преобразовать домен в рабочий диапазон, чтобы точно определить размер диаграммы, сохраняя при этом пропорции между точками данных.

-d3.scaleBand() и d3.scaleLinear() - это функции, которые отображают значения в системах координат и помещают данные в нужное место на экране.

  • ScaleBand () разбивает диапазон на полосы, вычисляет положение и ширину полос и применяет любое указанное заполнение.
  • ScaleLinear () строит непрерывную линейную шкалу с указанным доменом и диапазоном, сохраняя пропорциональные различия между точками данных.

-lineGenerator()конструирует строку по массиву координат.

4. Зададим домены для графиков:

y.domain(data.map(function(d) { return d.name }));
x.domain([0, d3.max(data, function(d) { return d.max })]);
x.nice();

Устанавливая домены x и y, мы просто объявляем полный набор значений для осей x и y, чтобы диаграмма знала, где начать и где закончиться (см. Обсуждение доменов и диапазонов выше).

5. Создадим оси, зададим несколько классов и добавим их в svg:

let yAxis = d3.axisLeft().scale(y)
  .tickSize(0);

let xAxis = d3.axisTop().scale(x)
  .tickFormat(function(d,i) {
  if (i == 0) {
    return "$0"
  } else {
    return d3.format(".2s")(d);
  }
});

let yAxisGroup = svg.append("g")
  .attr("class", "y-axis-group");

yAxisGroup.append("g")
  .attr("class", "y-axis")
  .attr("transform", "translate(-20, 0)")
  .call(yAxis)
  .select(".domain")
  .attr("opacity", 0);

let xAxisGroup = svg.append("g")
  .attr("class", "x-axis-group");

xAxisGroup.append("g")
  .attr("class", "x-axis")
  .call(xAxis);

let axisLines = svg.append("g")
  .attr("class", "grid-lines");

axisLines.selectAll("path")
  .data(x.ticks())
  .enter().append("path")
  .attr("class", "grid-line")
  .attr("d", axisLinePath);

.tickFormat форматирует отметки вручную. Мы передали ему функцию для отображения точек данных в удобочитаемом формате из двух значащих цифр (d3.format ('. 2s')).

6. Наконец, давайте создадим круги (леденцы), представляющие каждую точку данных, и добавим их на диаграмму. startcircles означает минимальное количество (заработок женщин) в каждой профессиональной категории, а endcircles - максимальное количество (заработок мужчин).

let lollipopsGroup = svg.append("g").attr("class", "lollipops");

let lollipops = lollipopsGroup.selectAll("g")
  .data(data)
  .enter().append("g")
  .attr("class", "lollipop")
  .attr("transform", function(d) {
  return "translate(0," + (y(d.name) + (y.bandwidth() / 2)) + ")";
})

lollipops.append("path")
  .attr("class", "lollipop-line")
  .attr("d", lollipopLinePath);

let startCircles = lollipops.append("circle")
  .attr("class", "lollipop-start")
  .attr("r", 5)
  .attr("cx", function(d) {
  return x(d.min);
});

let endCircles = lollipops.append("circle")
  .attr("class", "lollipop-end")
  .attr("r", 5)
  .attr("cx", function(d) {
  return x(d.max);
});

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

Спасибо за чтение! Если вам нужны сложные наборы данных и создание классных визуализаций данных, мы нанимаем.

Первоначально опубликовано на www.enigma.com 15 ноября 2018 г.