Использование TabularData для Dump Data Model

12 мая 2023

Фреймворк TabularData набирает обороты, обрабатывая таблицы данных для подготовки моделей машинного обучения. Но не позволяйте описанию на упаковке заставить вас оставить его в покое, под этим маленьким парнем скрывается огромная сила.

Например, фреймворк может быть использован для:

  1. Парсинг .csv файлов
  2. Парсинг .json файлы
  3. Импорт или экспорт
  4. Загружайте удивительные логи в свою консоль из ваших собственных моделей.

Его полезность выходит за рамки этого, но я хочу показать вам небольшой трюк, для которого я его использую, - в частности, касающийся пункта № 4 выше. Например, у тебя есть.json, подобный этому:


{
    "people":[
        {
            "name": "David",
            "mobile": 33333333,
            "hasPets": true,
            "pets": ["Dog", "Fish", "Bird"],
            "address": {
                "permanent": "France",
                "current": "UK"
            }
        },
        {
            "name": "Jordan",
            "mobile": 33333333,
            "hasPets": false,
            "pets": [],
            "address": {
                "permanent": "Austrailia",
                "current": "US"
            }
        }
    ]
}


Но представьте, что он больше, например….намного больше. Даже огромным? Скажем, 1000 или около того записей. Если бы вы должны были расшифровать такой ответ, используя пешеходные (pedestrian) модели, подобные этой:


import Foundation

struct Tennants: Codable {
    let renters: [Renter]
    
    enum CodingKeys: String, CodingKey {
        case renters = "people"
    }
}

struct Renter: Codable {
    let name: String
    let mobile: Int
    let hasPets: Bool
    let pets: [String]
    let address: Address
}

struct Address: Codable {
    let permanent, current: String
}

// Later on...
do {
    let decoder = JSONDecoder()
    let people: Tennants = try decoder.decode(Tennants.Type, 
                                              from: data)
    print(people)
} catch {
    Swift.debugPrint("Unable to decode response: \(error.localizedDescription)")
}

 

и просто print (или dump, .Swift.DebugPrint, Mirror(reflecting:)) в консоль, вы увидите что-то вроде этого:


Tennants(renters: [SwiftUI_Playgrounds.Renter(name: "David", mobile: 33333333, hasPets: true, pets: ["Dog", "Fish", "Bird"], address: SwiftUI_Playgrounds.Address(permanent: "France", current: "UK")), SwiftUI_Playgrounds.Renter(name: "Jordan", mobile: 33333333, hasPets: false, pets: [], address: SwiftUI_Playgrounds.Address(permanent: "Austrailia", current: "US")), SwiftUI_Playgrounds.Renter(name: "Peter", mobile: 33333333, hasPets: true, pets: ["Lizard"], address: SwiftUI_Playgrounds.Address(permanent: "India", current: "FR")), SwiftUI_Playgrounds.Renter(name: "Sarah", mobile: 33333333, hasPets: false, pets: [], address: SwiftUI_Playgrounds.Address(permanent: "Egypt", current: "US")), SwiftUI_Playgrounds.Renter(name: "Rory", mobile: 33333333, hasPets: true, pets: ["Snakes"], address: SwiftUI_Playgrounds.Address(permanent: "United Kingdom", current: "US"))])

 

В крайнем случае, это неплохо, и, безусловно, пригодно для использования. Но если вы поместите эти данные в DataFrame TabularData и выведете это? Что ж, давайте просто рискнем сказать, что это чуть приятнее. Гляньте на это:

Разве это не прекрасно😍?

Я начал использовать эту технику, когда любые из этих пунктов совпадают:

  • Я имею дело с большими объемами данных
  • Независимо от размера, если я хочу отсортировать или отфильтровать данные
  • Мне нужен простой способ визуализации данных, которые я получаю из своего собственного слоя модели
  • Я просто хочу, чтобы читать это было проще

... и я обожаю это. Код для его запуска тривиален:


do {
    let people = try await loadPeople()
    let data = try JSONEncoder().encode(people.renters)

    // Create the DataFrame from .json data
    let dataFrame = try DataFrame(jsonData: data)

    // Beautiful print
    print(dataFrame.description(options: .init(maximumLineWidth: 250)))
} catch {
    Swift.debugPrint("Unable to create DataFrame: \(error.localizedDescription)")
}

 

Обратите внимание на параметр options:. Там вы можете управлять шириной ячейки, параметрами форматирования даты, шириной строк и многим другим. Если вы хотите, чтобы гора данных была быстро переведена в удобный вас формат, хорошим вариантом является создание одноразовых экземпляров DataFrame.

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

Это не проблема, так как вы можете получить другой DataFrame из существующего, но укажите, что он должен включать только несколько интересующих вас столбцов:


do {
    let people = try await loadPeople()
    let data = try JSONEncoder().encode(people.renters)

    // Create the DataFrame from .json data
    let dataFrame = try DataFrame(jsonData: data)

    // Just get names and pets
    let partialFrame = dataFrame.selecting(columnNames: "name", "pets")
    print(partialFrame)
} catch {
    Swift.debugPrint("Unable to create DataFrame: \(error.localizedDescription)")
}

// Results in...
┏━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃   ┃ name     ┃ pets                                            ┃
┃   ┃ <String> ┃ <Array<Optional<Any>>>                          ┃
┡━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ 0 │ David    │ [Optional(Dog), Optional(Fish), Optional(Bird)] │
│ 1 │ Jordan   │ []                                              │
│ 2 │ Peter    │ [Optional(Lizard)]                              │
│ 3 │ Sarah    │ []                                              │
│ 4 │ Rory     │ [Optional(Snakes)]                              │
└───┴──────────┴─────────────────────────────────────────────────┘
5 rows, 2 columns

 

Там также есть так много разновидностей функциональных возможностей Swift, созданных специально для фреймворка. Вы поняли идею, но теперь предположим, что вам нужны были только имена, и чтобы они были отсортированны:


do {
    let people = try await loadPeople()
    let data = try JSONEncoder().encode(people.renters)

    // Create the DataFrame from .json data
    let dataFrame = try DataFrame(jsonData: data)

    // Select only names, and sort them
    let sortedNames = dataFrame.sorted(on: .init("name", String.self), by: { lhs, rhs in
        lhs < rhs
    }).selecting(columnNames: "name")
    print(sortedNames.description)
} catch {
    Swift.debugPrint("Unable to create DataFrame: \(error.localizedDescription)")
}

// Results in...
┏━━━┳━━━━━━━━━━┓
┃   ┃ name     ┃
┃   ┃ <String> ┃
┡━━━╇━━━━━━━━━━┩
│ 0 │ David    │
│ 1 │ Jordan   │
│ 2 │ Peter    │
│ 3 │ Rory     │
│ 4 │ Sarah    │
└───┴──────────┘
5 rows, 1 column

 

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

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

Предположим, у вас есть два столбца, представляющие долготу и широту, возвращаемые из некоторого источника данных. Вы могли бы объединить их в combineColumns (into:transform), чтобы создать экземпляры CLLocation для использования, пока кто-то ищет местоположение в строке поиска. Или вы могли бы выполнить SQL-подобные объединения двух разных экземпляров DataFrame, используя joined(on:).

Прекрасно.

let concatenatedThoughts = """

There are several ways to spin up a DataFrame, too. You can use local .csv or .json files, or their raw data and more. Be sure to check out the docs.

"""

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

Если вы хотите повозиться с ним прямо сейчас, чтобы запачкать руки, просто создайте DataFrame, используя словарный литерал, а затем вы можете поиграть с выгрузкой на консоль, как мы сделали здесь, или пощупать его API:


let testFrame: DataFrame = [
    "id": [0, 1, 2, 3, 4],
    "job": ["developer", "designer", "pm", "em", "staff SWE"],
    "fillBy": ["jordan", "jansyn", "bennett", "remy", "baylor"]
]
        
print(testFrame.description)

// Results in...
┏━━━┳━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━┓
┃   ┃ id    ┃ job       ┃ fillBy   ┃
┃   ┃ <Int> ┃ <String>  ┃ <String> ┃
┡━━━╇━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━┩
│ 0 │     0 │ developer │ jordan   │
│ 1 │     1 │ designer  │ jansyn   │
│ 2 │     2 │ pm        │ bennett  │
│ 3 │     3 │ em        │ remy     │
│ 4 │     4 │ staff SWE │ baylor   │
└───┴───────┴───────────┴──────────┘
5 rows, 3 columns

 

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

Возможно, Cupertino & Friends™️ не предполагали, что разработчики будут использовать фреймворк TabularData для... ведения журнала отладки? Это своего рода ситуация лингва франка, поскольку фреймворк, созданный для максимальной эффективности обучения моделей машинного обучения, пересекается и общается с людьми, которые просто хотят вывести “Это работает” в консоли.

Но все же мы здесь, и это невероятно полезно для нашей работы.

До скорого ✌️
 

Оригинал статьи

Содержание