.. _user_guide.introduction.vdf: The Virtual DataFrame ===================== The Virtual DataFrame (:py:mod:`~verticapy.vDataFrame`) is the core object of the VerticaPy library. Leveraging the power of Vertica and the flexibility of Python, the :py:mod:`~verticapy.vDataFrame` is a Python object that lets you manipulate the data representation in a Vertica database without modifying the underlying data. The data represented by a :py:mod:`~verticapy.vDataFrame` remains in the Vertica database, bypassing the limitations of working memory. When a :py:mod:`~verticapy.vDataFrame` is created or altered, VerticaPy formulates the operation as an SQL query and pushes the computation to the Vertica database, harnessing Vertica's massive parallel processing and in-built functions. Vertica then aggregates and returns the result to VerticaPy. In essence, vDataFrames behave similar to `views `_ in the Vertica database. For more information about Vertica's performance advantages, including its columnar orientation and parallelization across nodes, see the `Vertica documentation `_. In the following tutorial, we will introduce the basic functionality of the :py:mod:`~verticapy.vDataFrame` and then explore the ways in which they utilize in-database processing to enhance performance. Creating vDataFrames --------------------- First, run the :py:func:`~verticapy.datasets.load_titanic` function to ingest into Vertica a dataset with information about titanic passengers: .. code-block:: python from verticapy.datasets import load_titanic load_titanic() .. ipython:: python :suppress: from verticapy.datasets import load_titanic res = load_titanic() html_file = open("/project/data/VerticaPy/docs/figures/user_guide_introduction_best_practices_laod_titanic.html", "w") html_file.write(res._repr_html_()) html_file.close() .. raw:: html :file: /project/data/VerticaPy/docs/figures/user_guide_introduction_best_practices_laod_titanic.html You can create a :py:mod:`~verticapy.vDataFrame` from either an existing relation or a customized relation. To create a :py:mod:`~verticapy.vDataFrame` using an existing relation, in this case the Titanic dataset, provide the name of the dataset: .. code-block:: python import verticapy as vp vp.vDataFrame("public.titanic") To create a :py:mod:`~verticapy.vDataFrame` using a customized relation, specify the SQL query for that relation as the argument: .. code-block:: python vp.vDataFrame("SELECT pclass, AVG(survived) AS survived FROM titanic GROUP BY 1") .. ipython:: python :suppress: import verticapy as vp res = vp.vDataFrame("SELECT pclass, AVG(survived) AS survived FROM titanic GROUP BY 1") html_file = open("/project/data/VerticaPy/docs/figures/ug_intro_vdf_1.html", "w") html_file.write(res._repr_html_()) html_file.close() .. raw:: html :file: /project/data/VerticaPy/docs/figures/ug_intro_vdf_1.html For more examples of creating vDataFrames, see :py:mod:`~verticapy.vDataFrame`. In-memory vs. in-database -------------------------- The following examples demonstrate the performance advantages of loading and processing data in-database versus in-memory. First, we download the `Expedia dataset `_ from Kaggle and then load it into Vertica: .. note:: In this example, we are only showing the steps without actually computing the results due to the size of the database. If you perform the analysis, you'll notice a significant difference in performance. In-database processing is much faster. .. code-block:: python vp.read_csv( "expedia.csv", schema = "public", parse_nrows = 20000000, ) Once the data is loaded into the Vertica database, we can create a :py:mod:`~verticapy.vDataFrame` using the relation that contains the ``expedia`` dataset: .. code-block:: python import time start_time = time.time() expedia = vp.vDataFrame("public.expedia") print("elapsed time = {}".format(time.time() - start_time)) All the data—about 4GB—is stored in Vertica, requiring no in-memory data loading. Now, to compare the above result with in-memory loading, we load about half the dataset into pandas: .. note:: This process is expensive on local machines, so avoid running the following code if your computer has less than 2GB of memory. .. code-block:: python import pandas as pd start_time = time.time() expedia_df = pd.read_csv( "expedia.csv", ) print("elapsed time = {}".format(time.time() - start_time)) You will notice that it will take orders of magnitude more to load into memory compared with the time required to create the :py:mod:`~verticapy.vDataFrame`. Loading data into pandas is quite fast when the data volume is low (less than some MB), but as the size of the dataset increases, the load time can become exponentially more expensive. Even after the data is loaded into memory, the performance is very slow. The following example removes non-numeric columns from the dataset, then computes a correlation matrix: .. code-block:: python columns_to_drop = ["date_time", "srch_ci", "srch_co"] expedia_df = expedia_df.drop(columns_to_drop, axis = 1) start_time = time.time() expedia_df.corr() print(f"elapsed time = {time.time() - start_time}") Let's compare the performance in-database using a :py:mod:`~verticapy.vDataFrame` to compute the correlation matrix of the entire dataset: .. code-block:: python # Remove non-numeric columns expedia.drop(columns = ["date_time", "srch_ci", "srch_co"]) start_time = time.time() expedia.corr(show = False) print(f"elapsed time = {time.time() - start_time}") VerticaPy also caches the computed aggregations. With this cache available, we can repeat the correlation matrix computation almost instantaneously: .. note:: If necessary, you can deactivate the cache by calling the :py:func:`~verticapy.set_option` function with the ``cache`` parameter set to False. .. code-block:: python start_time = time.time() expedia.corr(show = False); print(f"elapsed time = {time.time() - start_time}") You will notice that the result can re-fetched instantaneously. Memory usage +++++++++++++ Now, we will examine how the memory usage compares between in-memory and in-database. First, use the pandas ``info()`` method to explore the DataFrame's memory usage: .. code-block:: python expedia_df.info() You should observe that the size is the same is that of the original file. Compare this with :py:mod:`~verticapy.vDataFrame` - The :py:mod:`~verticapy.vDataFrame` only uses about 37KB! By storing the data in the Vertica database, and only recording the user's data modifications in memory, the memory usage is reduced to a minimum. With VerticaPy, we can take advantage of Vertica's structure and scalability, providing fast queries without ever loading the data into memory. In the above examples, we've seen that in-memory processing is much more expensive in both computation and memory usage. This often leads to the decesion to downsample the data, which sacrfices the possibility of further data insights. The :py:mod:`~verticapy.vDataFrame` structure ---------------------------------------------- Now that we've seen the performance and memory benefits of the :py:mod:`~verticapy.vDataFrame` , let's dig into some of the underlying structures and methods that produce these great results. :py:mod:`~verticapy.vDataFrame` are composed of columns called :py:mod:`vDataColumn`. To view all :py:mod:`~verticapy.vDataColumn` in a :py:mod:`~verticapy.vDataFrame` , use the :py:func:`~verticapy.vDataFrame.get_columns` method: .. ipython:: python :suppress: vp.drop("public.expedia") vp.read_csv( "/project/data/VerticaPy/docs/source/_static/website/examples/data/booking/expedia.csv", schema = "public", parse_nrows = 20000000, ) expedia = vp.vDataFrame("public.expedia") .. ipython:: python expedia.get_columns() To access a :py:mod:`~verticapy.vDataColumn`, specify the column name in square brackets, for example: .. note:: VerticaPy saves computed aggregations to avoid unncessary recomputations. .. code-block:: python expedia["is_booking"].describe() .. ipython:: python :suppress: res = expedia["is_booking"].describe() html_file = open("/project/data/VerticaPy/docs/figures/ug_intro_vdf_describe.html", "w") html_file.write(res._repr_html_()) html_file.close() .. raw:: html :file: /project/data/VerticaPy/docs/figures/ug_intro_vdf_describe.html Each :py:mod:`~verticapy.vDataColumn` has its own catalog to save user modifications. In the previous example, we computed some aggregations for the ``is_booking`` column. Let's look at the catalog for that :py:mod:`~verticapy.vDataColumn`: .. ipython:: python expedia["is_booking"]._catalog The catalog is updated whenever major changes are made to the data. We can also view the vDataFrame's backend SQL code generation by setting the ``sql_on`` parameter to ``True`` with the :py:func:`~verticapy.set_option` function: .. code-block:: python vp.set_option("sql_on", True) expedia["cnt"].describe() .. code-block:: sql -- Computing the different aggregations SELECT /*+LABEL('vDataframe.aggregate')*/ APPROXIMATE_COUNT_DISTINCT("cnt") FROM ( SELECT "site_name", "posa_continent", "user_location_country", "user_location_region", "user_location_city", "orig_destination_distance", "user_id", "is_mobile", "is_package", "channel", "srch_adults_cnt", "srch_children_cnt", "srch_rm_cnt", "srch_destination_id", "srch_destination_type_id", "is_booking", "cnt", "hotel_continent", "hotel_country", "hotel_market", "hotel_cluster" FROM "public"."expedia" ) VERTICAPY_SUBTABLE LIMIT 1; -- Computing the descriptive statistics of all numerical columns using SUMMARIZE_NUMCOL SELECT /*+LABEL('vDataframe.describe')*/ SUMMARIZE_NUMCOL("cnt") OVER () FROM ( SELECT "site_name", "posa_continent", "user_location_country", "user_location_region", "user_location_city", "orig_destination_distance", "user_id", "is_mobile", "is_package", "channel", "srch_adults_cnt", "srch_children_cnt", "srch_rm_cnt", "srch_destination_id", "srch_destination_type_id", "is_booking", "cnt", "hotel_continent", "hotel_country", "hotel_market", "hotel_cluster" FROM "public"."expedia" ) VERTICAPY_SUBTABLE; .. ipython:: python :suppress: res = expedia["cnt"].describe() html_file = open("/project/data/VerticaPy/docs/figures/ug_intro_vdf_describe_cnt.html", "w") html_file.write(res._repr_html_()) html_file.close() .. raw:: html :file: /project/data/VerticaPy/docs/figures/ug_intro_vdf_describe_cnt.html To control whether each query outputs its elasped time, use the ``time_on`` parameter of the :py:func:`~verticapy.set_option` function: .. ipython:: python vp.set_option("sql_on", False) expedia = vp.vDataFrame("public.expedia") # creating a new vDataFrame to delete the catalog vp.set_option("time_on", True) expedia.corr() The aggregation's for each vDataColumn are saved to its catalog. If we again call the :py:func:`~verticapy.vDataFrame.corr` method, it'll complete in a couple seconds—the time needed to draw the graphic—because the aggregations have already been computed and saved during the last call: .. ipython:: python import time start_time = time.time() expedia.corr(); print("elapsed time = {}".format(time.time() - start_time)) To turn off the elapsed time and the SQL code generation options: .. ipython:: python vp.set_option("sql_on", False) vp.set_option("time_on", False) You can obtain the current :py:mod:`~verticapy.vDataFrame` relation with the :py:func:`~verticapy.vDataFrame.current_relation` method: .. ipython:: python print(expedia.current_relation()) The generated SQL for the relation changes according to the user's modifications. For example, if we impute the missing values of the ``orig_destination_distance`` vDataColumn by its average and then drop the ``is_package`` vDataColumn, these changes are reflected in the relation: .. ipython:: python expedia["orig_destination_distance"].fillna(method = "avg"); expedia["is_package"].drop(); print(expedia.current_relation()) Notice that the ``is_package`` column has been removed from the ``SELECT`` statement and the ``orig_destination_distance`` is now using a ``COALESCE`` SQL function. vDataFrame attributes and management ------------------------------------- The :py:mod:`~verticapy.vDataFrame` has many attributes and methods, some of which were demonstrated in the above examples. :py:mod:`~verticapy.vDataFrame` have two types of attributes: - Virtual Columns (:py:mod:`~verticapy.vDataColumn`) - Main attributes ( ``columns`` , ``main_relation`` ...) The :py:mod:`~verticapy.vDataFrame` main attributes are stored in the ``_vars`` dictionary: .. note:: You should never change these attributes manually. .. ipython:: python expedia._vars Data types ----------- :py:mod:`~verticapy.vDataFrame` use the data types of its :py:mod:`~verticapy.vDataColumn`. The behavior of some :py:mod:`~verticapy.vDataFrame` methods depend on the data type of the columns. For example, computing a histogram for a numerical data type is not the same as computing a histogram for a categorical data type. The :py:mod:`~verticapy.vDataFrame` identifies four main data types: - ``int``: integers are treated like categorical data types when their cardinality is low; otherwise, they are considered numeric - ``float``: numeric data types - ``date``: date-like data types (including timestamp) - ``text``: categorical data types Data types not included in the above list are automatically treated as categorical. You can examine the data types of the vDataColumns in a :py:mod:`~verticapy.vDataFrame` using the :py:func:`~verticapy.vDataFrame.dtypes` method: .. code-block:: python expedia.dtypes() .. ipython:: python :suppress: res = expedia.dtypes() html_file = open("/project/data/VerticaPy/docs/figures/ug_intro_vdf_expedia_dtypes.html", "w") html_file.write(res._repr_html_()) html_file.close() .. raw:: html :file: /project/data/VerticaPy/docs/figures/ug_intro_vdf_expedia_dtypes.html To convert the data type of a vDataColumn, use the :py:func:`~verticapy.vDataColumn.astype` method: .. ipython:: python expedia["hotel_market"].astype("varchar"); expedia["hotel_market"].ctype() To view the category of a specific :py:mod:`~verticapy.vDataColumn`, specify the :py:mod:`~verticapy.vDataColumn` and use the :py:func:`~verticapy.vDataColumn.category` method: .. ipython:: python expedia["hotel_market"].category() Exporting, saving, and loading ------------------------------- The :py:func:`~verticapy.vDataFrame.save` and :py:func:`~verticapy.vDataFrame.load` functions allow you to save and load vDataFrames: .. code-block:: python expedia.save() expedia.filter("is_booking = 1") .. ipython:: python :suppress: expedia.save() res = expedia.filter("is_booking = 1") html_file = open("/project/data/VerticaPy/docs/figures/ug_intro_vdf_expedia_filter.html", "w") html_file.write(res._repr_html_()) html_file.close() .. raw:: html :file: /project/data/VerticaPy/docs/figures/ug_intro_vdf_expedia_filter.html To return a :py:mod:`~verticapy.vDataFrame` to a previously saved structure, use the :py:func:`~verticapy.vDataFrame.load` function: .. ipython:: python expedia = expedia.load(); print(expedia.shape()) Because :py:mod:`~verticapy.vDataFrame` are views of data stored in the connected Vertica database, any modifications made to the :py:mod:`~verticapy.vDataFrame` are not reflected in the underlying data in the database. To save a :py:mod:`~verticapy.vDataFrame` relation to the database, use the :py:func:`~verticapy.vDataFrame.to_db` method. It's good practice to examine the expected disk usage of the :py:mod:`~verticapy.vDataFrame` before exporting it to the database: .. code-block:: python expedia.expected_store_usage(unit = "Gb") .. ipython:: python :suppress: res = expedia.expected_store_usage(unit = "Gb") html_file = open("/project/data/VerticaPy/docs/figures/ug_intro_vdf_expedia_storage_gb.html", "w") html_file.write(res._repr_html_()) html_file.close() .. raw:: html :file: /project/data/VerticaPy/docs/figures/ug_intro_vdf_expedia_storage_gb.html If you decide that there is sufficient space to store the :py:mod:`~verticapy.vDataFrame` in the database, run the :py:func:`~verticapy.vDataFrame.to_db` method: .. code-block:: python expedia.to_db( "public.expedia_clean", relation_type = "table", )