Loading...

Complex Data Types & VMaps

Setup

In order to work with complex data types in VerticaPy, you’ll need to complete the following three setup tasks:

  • Import relevant libraries:

import verticapy as vp
  • Connect to Vertica. This example uses an existing connection called VerticaDSN . For details on how to create a connection, see the Connection tutorial.

Note

You can skip the below cell if you already have an established connection.

vp.connect("VerticaDSN")

Check your VerticaPy version to make sure you have access to the right functions:

import verticapy as vp

vp.__version__
Out[2]: '1.1.0'

You can make it easier to keep track of your work by creating a custom schema:

Note

Because some tables are repeated in this demonstration, tables with the same names are dropped.

vp.drop("complex_vmap_test", method = "schema")
Out[3]: False

vp.create_schema("complex_vmap_test")
Out[4]: True

We also set the path to our data:

path= "/home/dbadmin/"

You can download the demo datasets from here.

Loading Complex Data

There are two ways to load a nested data file:

  • Load directly using read_json(). In this case, you will need to use an additional parameter to identify all the data types. The function loads the data using flex tables and VMaps (Native Vertica MAPS, which are flexible but not optimally performant).

  • Load using read_file(). The function predicts the complex data structure.

Let’s try both:

data = vp.read_json(
    path + "laliga/2008.json",
    schema = "public",
    ingest_local = False,
    use_complex_dt = True,
)
123
away_score
Int
100%
...
🛠
Row(data_version date,shot_fidelity_version int,xy_fidelity_version int)
90%
🛠
Row(season_id int,season_name varchar(80))
100%
10...
20...
30...
40...
50...
60...
70...
80...
90...
100...
110...
120...
130...
140...
150...
160...
170...
180...
190...
200...
210...
220...
230...
240...
250...
260...
270...
280...
290...
300...
310...
320...
330...
340...
350...
360...
370...
380...
390...
400...
410...
420...
430...
440...
450...
460...
470...
481...
491...
501...
511...
521...
531...
541...
551...
561...
571...
581...
591...
601...
611...
621...
631...
641...
651...
661...
671...
681...
691...
701...
711...
721...
731...
741...
751...
761...
771...
781...
791...
802...
812...
822...
832...
842...
852...
862...
872...
882...
892...
902...
912...
922...
932...
942...
952...
962...
972...
982...
993...
1003...

Similar to the use of read_json() above, we can use read_file() to ingest the complex data directly:

data = vp.read_file(
    path = path + "laliga/2005.json",
    ingest_local = False,
    schema = "complex_vmap_test",
)
data.head(100)
123
away_score
Int
100%
...
🛠
Row(data_version date,shot_fidelity_version int,xy_fidelity_version int)
90%
🛠
Row(season_id int,season_name varchar(80))
100%
10...
20...
30...
40...
50...
60...
70...
80...
90...
100...
110...
120...
130...
140...
150...
160...
170...
180...
190...
200...
210...
220...
230...
240...
250...
260...
270...
280...
290...
300...
310...
320...
330...
340...
350...
360...
370...
380...
390...
400...
410...
420...
430...
440...
450...
460...
470...
481...
491...
501...
511...
521...
531...
541...
551...
561...
571...
581...
591...
601...
611...
621...
631...
641...
651...
661...
671...
681...
691...
701...
711...
721...
731...
741...
751...
761...
771...
781...
791...
802...
812...
822...
832...
842...
852...
862...
872...
882...
892...
902...
912...
922...
932...
942...
952...
962...
972...
982...
993...
1003...

We can also use the handy genSQL parameter to generate (but not execute) the SQL needed to create the final relation:

Note

This is a great way to customize the data ingestion or alter the final relation types.

data = vp.read_json(
    path + "laliga/2008.json",
    schema = "public",
    ingest_local = False,
    use_complex_dt = True,
    genSQL = True,
)
CREATE TABLE "complex_vmap_test"."laliga_2005" (
    "away_score" FLOAT,
    "away_team" ROW(
        "away_team_gender" VARCHAR(60),
        "away_team_group" VARCHAR(60),
        "away_team_id" INT,
        "away_team_name" VARCHAR(60),
        "country" ROW(
            "id" INT,
            "name" VARCHAR(60)
        )
    ),
    "competition" ROW(
        "competition_id" INT,
        "competition_name" VARCHAR(60),
        "country_name" VARCHAR(60)
    ),
    "competition_stage" ROW(
        "id" INT,
        "name" VARCHAR(60)
    ),
    "home_score" INT,
    "home_team" ROW(
        "country" ROW(
            "id" INT,
            "name" VARCHAR(60)
        ),
        "home_team_gender" VARCHAR(60),
        "home_team_group" VARCHAR(60),
        "home_team_id" INT,
        "home_team_name" VARCHAR(60)
    ),
    "kick_off" TIME,
    "last_updated" DATE,
    "match_date" DATE,
    "match_id" INT,
    "match_status" VARCHAR(60),
    "match_week" INT,
    "metadata" ROW(
        "data_version" DATE,
        "shot_fidelity_version" INT,
        "xy_fidelity_version" INT
    ),
    "season" ROW(
        "season_id" INT,
        "season_name" VARCHAR(60)
    )
);

COPY "complex_vmap_test"."laliga_2005"
FROM '/scratch_b/qa/ericsson/laliga/2005.json'
PARSER FJsonParser();

Feature Exploration

In the generated SQL from the above example, we can see that the away_team column is a ROW type with a complex structure consisting of many sub-columns. We can convert this column into a JSON and view its contents:

data["competition_stage"].astype("json")
123
away_score
Int
100%
...
🛠
Row(away_team_gender varchar(80),away_team_group varchar(80),away_team_id int,away_team_name varchar(80),country row(id int,name
100%
🛠
Row(season_id int,season_name varchar(80))
100%
10...
20...
30...
40...
50...
60...
70...
80...
90...
100...
110...
120...
130...
140...
150...
160...
170...
180...
190...
200...

As with a normal vDataFrame, we can easily extract the values from the sub-columns:

data["away_team"]["away_team_gender"]
Abc
away_team_gender
Varchar(80)
1male
2male
3male
4male
5male
6male
7male
8male
9male
10male
11male
12male
13male
14male
15male
16male
17male
18male
19male
20male

We can view any nested data structure by index:

ddata["competition"]["competition_id"]
123
competition_id
Integer
111
211
311
411
511
611
711
811
911
1011
1111
1211
1311
1411
1511
1611
1711
1811
1911
2011

These nested structures can be used to create features:

data["name_home"] = data["home_team"]["home_team_name"];

We can even flatten the nested structure inside a json file, either flattening the entire file or just particular columns:

data = vp.read_json(
    path = path + "laliga/2008.json",
    table_name = "laliga_flat",
    schema = "complex_vmap_test",
    ingest_local = False,
    flatten_maps = True,
)
data.head(100)
🛠
Vmap(378)
54%
...
123
competition.competition_id
Int
100%
Abc
away_team.country.name
Varchar(20)
100%
1...11Spain
2...11Spain
3...11Spain
4...11Spain
5...11Spain
6...11Spain
7...11Spain
8...11Spain
9...11Spain
10...11Spain
11...11Spain
12...11Spain
13...11Spain
14...11Spain
15...11Spain
16...11Spain
17...11Spain
18...11Spain
19...11Spain
20...11Spain
21...11Spain
22...11Spain
23...11Spain
24...11Spain
25...11Spain
26...11Spain
27...11Spain
28...11Spain
29...11Spain
30...11Spain
31...11Spain

We can see that all the columns from the JSON file have been flattened and multiple columns have been created for each sub-column. This causes some loss in data structure, but makes it easy to see the data and to use it for model building.

It is important to note that the data type of certain columns (home_team.managers) is now VMap, and not the ROW type that we saw in the above cells. Even though both are used to capture nested data, there is in a subtile difference between the two.

  • VMap: More flexible as it stores the data as a string of maps, allowing the ingestion of data in varying shapes. The shape is not fixed and new keys can easily be handled. This is a great option when we don’t know the structure in advance, or if the structure changes over time.

  • Row: More rigid because the dictionaries, including all the data types, are fixed when they are defined. Newly parsed keys are ignored. But because of it’s rigid structure, it is much more performant than VMaps. They are best used when the file structure is known in advance.

To deconvolve the nested structure, we can use the flatten_arrays parameter in order to make the output strictly formatted. However, it can be an expensive process.

data = vp.read_json(
    path = path + "laliga/2008.json",
    table_name = "laliga_flat",
    schema = "complex_vmap_test",
    ingest_local = False,
    flatten_arrays=True,
)
data.head(100)
Abc
home_team.managers.0.nickname
Varchar(42)
54%
...
Abc
away_team.away_team_group
Varchar(20)
0%
123
away_score
Int
100%
1Manuel Pellegrini...[null]2
2Pep Guardiola...[null]1
3Pep Guardiola...[null]1
4Pep Guardiola...[null]0
5Juande Ramos...[null]6
6Lucas Alcaraz...[null]2
7Manolo Jiménez...[null]3
8Pep Guardiola...[null]0
9Pep Guardiola...[null]2
10Pep Guardiola...[null]0
11[null]...[null]6
12[null]...[null]3
13José Luis Mendilibar...[null]1
14Juan Muñiz...[null]2
15Pep Guardiola...[null]0
16Pep Guardiola...[null]0
17Pep Guardiola...[null]3
18[null]...[null]3
19[null]...[null]0
20[null]...[null]0
21[null]...[null]1
22Pep Guardiola...[null]0
23Unai Emery...[null]2
24[null]...[null]2
25[null]...[null]2
26[null]...[null]2
27[null]...[null]1
28[null]...[null]1
29[null]...[null]4
30[null]...[null]0
31[null]...[null]2

We can even convert columns into other formats, such as string:

data["home_team.managers.0.nickname"].astype(str)
Abc
home_team.managers.0.nickname
Varchar
54%
...
Abc
home_team.managers.0.name
Varchar(64)
54%
123
away_score
Int
100%
1Manuel Pellegrini...Manuel Luis Pellegrini Ripamonti2
2Pep Guardiola...Josep Guardiola i Sala1
3Pep Guardiola...Josep Guardiola i Sala1
4Pep Guardiola...Josep Guardiola i Sala0
5Juande Ramos...Juan de la Cruz Ramos Cano6
6Lucas Alcaraz...Luis Lucas Alcaraz González2
7Manolo Jiménez...Manuel Enrique Jiménez Jiménez3
8Pep Guardiola...Josep Guardiola i Sala0
9Pep Guardiola...Josep Guardiola i Sala2
10Pep Guardiola...Josep Guardiola i Sala0
11[null]...[null]6
12[null]...[null]3
13José Luis Mendilibar...José Luis Mendilibar Etxebarria1
14Juan Muñiz...Juan Ramón López Muñiz2
15Pep Guardiola...Josep Guardiola i Sala0
16Pep Guardiola...Josep Guardiola i Sala0
17Pep Guardiola...Josep Guardiola i Sala3
18[null]...[null]3
19[null]...[null]0
20[null]...[null]0

Or integer:

data["match_week"].astype(int)
Abc
home_team.managers.0.nickname
Varchar
54%
...
Abc
home_team.managers.0.name
Varchar(64)
54%
123
away_score
Int
100%
1Manuel Pellegrini...Manuel Luis Pellegrini Ripamonti2
2Pep Guardiola...Josep Guardiola i Sala1
3Pep Guardiola...Josep Guardiola i Sala1
4Pep Guardiola...Josep Guardiola i Sala0
5José Luis Mendilibar...José Luis Mendilibar Etxebarria1
6Juan Muñiz...Juan Ramón López Muñiz2
7Pep Guardiola...Josep Guardiola i Sala0
8Pep Guardiola...Josep Guardiola i Sala0
9Pep Guardiola...Josep Guardiola i Sala3
10[null]...[null]3
11[null]...[null]0
12[null]...[null]0
13[null]...[null]1
14Juande Ramos...Juan de la Cruz Ramos Cano6
15Lucas Alcaraz...Luis Lucas Alcaraz González2
16Manolo Jiménez...Manuel Enrique Jiménez Jiménez3
17Pep Guardiola...Josep Guardiola i Sala0
18Pep Guardiola...Josep Guardiola i Sala2
19Pep Guardiola...Josep Guardiola i Sala0
20[null]...[null]6

It is also possible to:

  • Cast str to array.

  • Cast complex data types to json str.

  • Cast str to VMAP

  • And much more…

Multiple File Ingestion

If we have multiple files with the same extension, we can easily ingest them using the * operator:

data = vp.read_file(
    path = path + "laliga/*.json",
    table_name = "laliga_all",
    ingest_local = False,
    schema = "complex_vmap_test",
)

We can also do this for other file types. For example, CSV:

data = vp.read_csv(
    path = path + "*.csv",
    table_name = "cities_all",
    schema = "complex_vmap_test",
    ingest_local = False,
    insert = True,
)

Materialize

When we do not materialize a table, it automatically becomes a flextable:

data = vp.read_json(
    path = path + "laliga/*.json",
    table_name = "laliga_verticapy_test_json",
    schema = "complex_vmap_test",
    ingest_local = False,
    materialize = False,
)
data.head(100)
123
referee.country.id
Integer
22%
...
Abc
competition.country_name
Varchar(20)
100%
123
away_team.away_team_id
Integer
100%
1112...Spain217
2112...Spain215
3214...Spain217
4112...Spain322
5112...Spain217
6112...Spain216
7214...Spain212
8112...Spain208
9112...Spain217
10112...Spain222
11112...Spain217
12112...Spain207
13112...Spain217
14112...Spain217
15112...Spain206
16214...Spain214
17214...Spain219
18112...Spain220
19112...Spain217
20112...Spain210
21112...Spain217
22112...Spain217
23112...Spain211
24112...Spain217
25112...Spain223
26112...Spain213
27112...Spain217
28112...Spain217
29112...Spain217
30112...Spain221
31112...Spain209
32112...Spain217
33112...Spain205
34214...Spain217
35112...Spain218
36214...Spain217
37[null]...Spain217
38[null]...Spain216
39[null]...Spain210
40[null]...Spain217
41[null]...Spain222
42[null]...Spain220
43[null]...Spain395
44[null]...Spain1217
45[null]...Spain217
46[null]...Spain217
47[null]...Spain213
48[null]...Spain217
49[null]...Spain1218
50[null]...Spain217
51[null]...Spain217
52[null]...Spain215
53[null]...Spain223
54[null]...Spain217
55[null]...Spain217
56[null]...Spain217
57[null]...Spain217
58[null]...Spain217
59[null]...Spain207
60[null]...Spain217
61[null]...Spain217
62[null]...Spain214
63[null]...Spain1043
64[null]...Spain217
65[null]...Spain422
66[null]...Spain403
67[null]...Spain217
68[null]...Spain212
69[null]...Spain217
70[null]...Spain219
71[null]...Spain217
72[null]...Spain217
73[null]...Spain217
74[null]...Spain217
75[null]...Spain220
76[null]...Spain207
77[null]...Spain422
78[null]...Spain217
79[null]...Spain395
80[null]...Spain213
81[null]...Spain217
82[null]...Spain217
83[null]...Spain217
84[null]...Spain215
85[null]...Spain217
86[null]...Spain214
87[null]...Spain218
88[null]...Spain217
89[null]...Spain221
90[null]...Spain1043
91[null]...Spain216
92[null]...Spain217
93[null]...Spain1217
94[null]...Spain217
95[null]...Spain217
96[null]...Spain360
97[null]...Spain217
98[null]...Spain210
99[null]...Spain217
100[null]...Spain215

Some of the columns are VMAPs:

managers = ["away_team.managers", "home_team.managers"]

for m in managers:
    print(data[m].isvmap())

True
True

We can easily flatten the VMaps virtual columns by using the flat_vmap() method:

data.flat_vmap(managers).drop(managers)
123
referee.country.id
Integer
22%
...
Abc
referee.country.name
Varchar(20)
22%
123
away_team.away_team_id
Integer
100%
1112...Italy217
2112...Italy215
3214...Spain217
4112...Italy322
5112...Italy217
6112...Italy216
7214...Spain212
8112...Italy208
9112...Italy217
10112...Italy222
11112...Italy217
12112...Italy207
13112...Italy217
14112...Italy217
15112...Italy206
16214...Spain214
17214...Spain219
18112...Italy220
19112...Italy217
20112...Italy210

To check for a flex table, we can use the following function:

from verticapy.sql import isflextable

isflextable(table_name = "laliga_verticapy_test_json", schema = "complex_vmap_test")
Out[9]: True

We can then manually materialize the flextable using the convenient to_db() method:

data.to_db("complex_vmap_test.laliga_to_db");

Once we have stored the database, we can easily create a vDataFrame of the relation:

data_new = vp.vDataFrame("complex_vmap_test.laliga_to_db")

Transformations

First, we load the dataset.

from verticapy.datasets import load_amazon

data = load_amazon()
📅
date
Date
100%
...
Abc
state
Varchar(32)
100%
123
number
Int
100%
11998-01-01...AMAPÁ0
21998-01-01...AMAZONAS0
31998-01-01...DISTRITO FEDERAL0
41998-01-01...ESPÍRITO SANTO0
51998-01-01...MARANHÃO0
61998-01-01...PARANÁ0
71998-01-01...PIAUÍ0
81998-01-01...RORAIMA0
91998-01-01...SERGIPE0
101998-01-01...SÃO PAULO0
111998-02-01...GOIÁS0
121998-02-01...MATO GROSSO DO SUL0
131998-02-01...MINAS GERAIS0
141998-02-01...PARAÍBA0
151998-02-01...SANTA CATARINA0
161998-02-01...SÃO PAULO0
171998-03-01...AMAPÁ0
181998-03-01...BAHIA0
191998-03-01...MATO GROSSO DO SUL0
201998-03-01...PARÁ0

Once we have data in the form of vDataFrame, we can readily convert it to a JSON file:

data.to_json(path = "amazon_json.json")

Now we can load the new JSON file and see the contents:

data = read_json(
    path = "amazon_json.json",
    schema = "complex_vmap_test",
    table_name = "cities_transf_test",
    ingest_local = False,
)

We can even extract the JSON as string and edit it before saving it as a json file:

json_str = data.to_json();

Let’s look at the begining portion of the string:

json_str[0:100]
Out[14]: '[\n{"date": "1998-02-01", "state": "ALAGOAS", "number": 0},\n{"date": "1998-02-01", "state": "BAHIA", '

We can edit a portion of the string and save it again. We’ll change the name of the first State from ACRE to XXXX:

json_str = json_str[:35] + 'XXXX' + json_str[39:];

Now we can save this edited strings file:

out_file = open(path + "amazon_edited.json", "w")

out_file.write(json_str)
Out[17]: 391613

out_file.close()

If we look at the new file, we can see the updated changes:

data = vp.read_json(
    path = path + "amazon_edited.json",
    schema = "complex_vmap_test",
    table_name = "amazon_edit",
    ingest_local = True,
);

The table "complex_vmap_test"."amazon_edit" has been successfully created.

Let’s search for the changed name:

data[data["state"] == "XXXX"]
123
number
Integer
100%
...
Abc
state
Varchar(38)
100%
📅
date
Date
100%
10...XXXXOAS1998-02-01

Now to clean everything up, we can drop our temporary schema:

vp.drop("complex_vmap_test", method = "schema")
Out[20]: True

Conclusion

This new functionality not only make it easy to ingest complex data types in different formats, but it enables data wrangling like never before.

The new features provide increased flexibility while keeping the process and syntax simple. You can do all of the following in VerticaPy:

  • Ingest complex datasets.

  • Perform convenient column operations.

  • Switch data types.

  • Flatten columns and maps into array like structures.