# Welcome To Zingg

Hope you find us useful :-)

This is the documentation for Zingg Community v0.7 .

## Why?

Data silos hurt all business functions - customer analytics, supplier consolidation, risk and compliance, and sales and marketing.

![Data Silos](https://524808071-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGh6QkdUgeylq0gyuMmBw%2Fuploads%2Fgit-blob-4103cd4fa2843281b0c9fb64626cb7f3646f0d54%2FdataSilos.png?alt=media)

Zingg is a quick and scalable way to build a single source of truth for core business entities. With Zingg, the analytics engineer and the data scientist can quickly integrate data silos and build unified views at scale!

![# Zingg - Data Mastering At Scale with ML](https://524808071-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGh6QkdUgeylq0gyuMmBw%2Fuploads%2Fgit-blob-092139c825b37b78c3930cd17d61284bd7c53a7b%2FdataMastering.png?alt=media)

<figure><img src="https://static.scarf.sh/a.png?x-pxid=b96858f2-6fe2-4fdf-a68b-946527ca3daf" alt=""><figcaption></figcaption></figure>

## Ask For Help

If you want to understand if Zingg is a fit for your use case or have other questions or suggestions, join our [Slack.](https://join.slack.com/t/zinggai/shared_invite/zt-w7zlcnol-vEuqU9m~Q56kLLUVxRgpOA)


# Step-By-Step Guide

Instructions on how to install and use Zingg

## Step 1: Install

Installation instructions for docker, as well as GitHub release, are [here](/latest/stepbystep/installation). If you need to build from the sources or compile for a different flavor of Spark, check [compiling](/latest/stepbystep/installation#compiling-from-sources).

## Step 2: Plan For Hardware

Decide your hardware based on the [performance numbers](/latest/stepbystep/hardwaresizing).

## Step 3: Build The Config For Your Data

Zingg needs a configuration file that defines the data and what kind of matching is needed. You can create the configuration file by following the instructions [here](/latest/stepbystep/configuration).

## Step 4: Create Training Data

Zingg builds a new set of models(blocking and similarity) for every new schema definition(columns and match types). This means running the *findTrainingData* and *label* phases multiple times to build the training dataset from which Zingg will learn. You can read more [here](/latest/stepbystep/createtrainingdata).

## Step 5: Build & Save The Model

The training data in Step 4 above is used to train Zingg and build and save the models. This is done by running the *train* phase. Read more [here](/latest/stepbystep/train).

## Step 6: Voila, Let's Match!

It's now time to apply the model to our data. This is done by running the *match* or the *link* phases depending on whether you are matching within a single source or linking multiple sources respectively. You can read more about [matching](/latest/stepbystep/match) and [linking](/latest/stepbystep/link).

As long as your input columns and the field types are not changing, the same model should work and you do not need to build a new model. If you change the match type, you can continue to use the training data and add more labeled pairs on top of it.


# Installation

From the pre-built release hosted on GitHub

Zingg runs on [Spark](https://spark.apache.org) and can be used on all major Spark distributions. Zingg can run on all major Linux flavors.

Zingg can be installed from the [release on GitHub](/latest/stepbystep/installation/installing-from-release), [built from sources](/latest/stepbystep/installation/compiling-from-source) or used [directly through Docker](/latest/stepbystep/installation/docker). The next sections describe each option in more detail.


# Docker

From pre-built Docker image with all dependencies included

## Running From Docker Image From Docker Hub

The easiest way to get started is to pull the Docker image with the last release of Zingg.

```
docker pull zingg/zingg:0.5.0
docker run -it zingg/zingg:0.5.0 bash
```

In case of permission denied, try mapping /tmp of docker with user's machine /tmp

```
docker run -v /tmp:/tmp -it zingg/zingg:0.5.0 bash
```

To know more about Docker, please refer to the official [Docker documentation](https://docs.docker.com/).

##


# Sharing Custom Data And Config Files

Using custom data to save data files on host machine

However, note that once the docker container is stopped, all the work done in that session is lost. If we want to use custom data or persist the generated model or data files, we have to use **Volumes** or **Bind Mount** to share files between the two.

```
docker run -v <local-location>:<container-location> -it zingg/zingg:0.5.0 bash
```

The **\<local-location>** directory from host will get mounted inside container at **\<container-location>**. Any file written inside this directory will persist on the host machine and can be reused in a new container instance later.


# Shared Locations

Shared location used to store Zingg configurations

## Zingg Configurations Using Shared Location

The **zinggDir** location where model information is stored may use a shared location. In fact, any *oft-editable* file such as **config.json** should be kept in this location only.

```
zingg.sh --phase label --conf config.json --zinggDir /location
```

Similarly, the output and data dir [configurations](/latest/stepbystep/configuration) inside **config.json** can be made using a shared location. Please ensure that the running user has access permissions for this location.


# File Read/Write Permissions

To enable user to have create/read/write files in shared location

A docker image is preferred to run with a non-root user. By default, the Zingg container runs with **uid '1001'**. A valid '*uid*' can be passed through the command line in order to run the container with that user id. This will enable the user to have requisite permissions to create/read/write files in the shared location.

```
$ id 
uid=1000(abc) gid=1000(abc) groups=1000(abc)
$ docker run -u <uid> -it zingg/zingg:0.4.1-SNAPSHOT bash
```


# Copying Files To And From The Container

Alternative to volume/bind mount

A quick alternative to **Volume/Bind Mount** is to just copy necessary files to and forth between the host and the container.

One specific file/directory can be copied TO and FROM the container. e.g.

```
$ docker cp foo.txt <container_id>:/foo.txt
$ docker cp <container_id>:/foo.txt foo.txt
```

The *container id* of the running instance can be found using the below command:

```
$ docker ps
```


# Installing From Release

From the pre-built release hosted on GitHub

Zingg is prebuilt for common Spark versions so that you can use those directly. The following document assumes that we are installing **Zingg 0.5.0** on **Spark 3.5.0**, but you can follow the same process for other versions too.

## Prerequisites

A) Java JDK - version "11.0.23"

B) Apache Spark - version spark-3.5.0-bin-hadoop3

***

####


# Single Machine Setup

For standalone local Spark without a cluster

Zingg can be easily run on a single machine to process up to a few million records.

To prepare your machine, please do the following steps:

A) Install the specified **JDK** version

B) Apache Spark - Download the specified version from spark.apache.org and unzip it in a folder under home

Please add the following entries to respective directory. Linux - `~/.bash_aliases` macOS - `~/.zshrc`

> `export JAVA_HOME=path to jdk`

> `export SPARK_HOME=path to location of Apache Spark`

> `export SPARK_MASTER=local[*]`

C) Correct entry of host under `/etc/hosts`

Run `ifconfig` to find the IP of the machine and make sure it is added to the `/etc/hosts` for localhost.


# Spark Cluster Checklist

To configure Zingg in a Spark cluster

If you have a ready Spark cluster, you can run Zingg by configuring the following environment on your driver machine:

> `export JAVA_HOME=path to jdk`

> `export SPARK_HOME=path to Apache Spark`

> `export SPARK_MASTER=spark://master-host:master-port`


# Installing Zingg

Downloading and setting things up

Download the tar zingg-version.tar.gz from the [Zingg releases page](https://github.com/zinggAI/zingg/releases) to a folder of your choice and run the following:

> `gzip -d zingg-0.5.0.tar.gz ; tar xvf zingg-0.5.0.tar`

This will create a folder *zingg-0.5.0* under the chosen folder.

Move the above folder to zingg.

> `mv zingg-0.5.0 ~/zingg`

> `export ZINGG_HOME=path to zingg`

> `export PATH=$PATH:$JAVA_HOME/bin:$SPARK_HOME/bin:$SPARK_HOME/sbin:$ZINGG_HOME/scripts`


# Verifying The Installation

To verify the Zingg installation works fine

Run bash and print the aliases to ensure that they are set correctly.

> `bash`

> `echo $SPARK_HOME`

> `echo $JAVA_HOME`

> `java --version`

> `echo $ZINGG_HOME`

Let us now run a sample program to ensure that our installation is correct.

> `cd zingg`

> `./scripts/zingg.sh --phase trainMatch --conf examples/febrl/config.json`

The above will build Zingg models and use that to find duplicates in the **examples/febrl/test.csv** file. You will see Zingg logs on the console and once the job finishes, you will see some files under **/tmp/zinggOutput** with matching records sharing the same *cluster id*.

Congratulations, Zingg has been installed!


# Enterprise Installation for Snowflake

From the jars provided

## Prerequisites

A) Java JDK 11

B) Snowflake - ZINGG\_STAGE created in Enterprise Snowflake account

***

####


# Setting up Zingg

Copy the release and license to a folder of your choice. Say directly under /home/ubuntu. Then execute the following:

> `gzip -d zingg-enterprise-snowflake-0.4.1-SNAPSHOT.tar.gz`

> `tar xvf zingg-enterprise-snowflake-0.4.1-SNAPSHOT.tar`

> `cd zingg-enterprise-snowflake-0.4.1-SNAPSHOT`

> `export ZINGG_SNOW_JAR=~/zingg-enterprise-snowflake-0.4.1-SNAPSHOT`

> `export ZINGG_SNOW_HOME=~/zingg-enterprise-snowflake-0.4.1-SNAPSHOT`

**It is better to keep ZINGG\_SNOW\_JAR and ZINGG\_SNOW\_HOME as part of the .bashrc for always having this value as part of the shell**

> `mv ~/zingg.license .`


# Snowflake Properties

Connection Details

Zingg needs details about accessing Snowflake which can be provided through a properties file.

> `touch snowEnv.txt`

### SnowEnv.txt format:

```
    URL={snowflake_url}   
    USER={snowflake_user_name} 
    PASSWORD={snowflake_password}  
    ROLE={role} 
    WAREHOUSE={warehouse}  
    DB={database_name}  
    SCHEMA={schema}
    CLIENT_SESSION_KEEP_ALIVE_HEARTBEAT_FREQUENCY=900
```

The parameter CLIENT\_SESSION\_KEEP\_ALIVE\_HEARTBEAT\_FREQUENCY is the number of seconds in-between client attempts to update the token for the session - it can vary from 900 to 3600.


# Match Configuration

Configuring Zingg Enterprise For Snowflake

Create the snowflake config file. This config file will contain the model locations, match types defined on fields, input and output tables in Snowflake. Please refer to **examples/febrl/configSnow\.json** for a sample. Documentation for fields and match types is defined at [Zingg Field Definitions](https://docs.zingg.ai/zingg0.5.0/stepbystep/configuration/field-definitions).

Along with the changes to the field definitions, please do the following:

* Give ‘modelId’ a name. An example could be 28NovDev.
* Change ‘INPUT\_TABLE\_NAME’ in data with the name of source table
* Change ‘OUTPUT\_TABLE\_NAME’ in output with the name of output table


# Running Asynchronously

To ensure Zingg works as a background process

Using no-hup mode, we can run Zingg as a background process and even if SSH is broken it will continue to work.

> `nohup ./scripts/zingg.sh --properties-file ~/zingg/snowEnv.txt --phase findTrainingData --conf ~/zingg/snowConfigFile.json &`

We can see the logs of scripts by running the following command:

> `tail -f nohup.out`


# Verifying The Installation

To verify that Zingg Enterprise installation works perfectly

Let us now run a sample program to ensure that our installation is correct.

> `./scripts/zingg.sh --properties-file snowEnv.txt --phase findTrainingData --conf examples/febrl/configSnow.json`

The above will build Zingg models and use that to find duplicates in the **examples/febrl/test.csv** file. You will see Zingg logs on the console and once the job finishes, you will see tables being formed with the name **UNIFIED\_CUSTOMERS\_MODELID** with matching records sharing the same *cluster id*.

Congratulations, Zingg has been installed!


# Compiling From Source

For a different Spark version or compiling latest code

If you need to compile the latest code or build for a different Spark version, you can clone this [repo](https://github.com/zinggAI/zingg/tree/main) and

* Install **maven**
* Install **JDK 1.8**
* Set `JAVA_HOME` to JDK base directory
* Install **Apache Spark (version spark-3.5.0-bin-hadoop3)**
* Set `SPARK_HOME` as path to location of Apache Spark
* Set `SPARK_MASTER` as **local\[\*]**
* Run the following: `mvn clean install -U` -> `mvn initialize` -> `mvn clean compile package`


# Hardware Sizing

Hardware required for different sizes of data

Zingg has been built to scale. Performance is dependent on:

* The **number of records** to be matched.
* The **number of fields** to be compared against each other.
* The **actual number** of duplicates.

Here are some performance numbers you can use to determine the appropriate hardware for your data.

* 120k records of **examples/febrl120k/test.csv** take 5 minutes to run on a 4 core, 10 GB RAM local Spark cluster.
* 5m records of [North Carolina Voters](https://github.com/zinggAI/zingg/tree/main/examples/ncVoters5M) take \~4 hours on a 4 core, 10 GB RAM local Spark cluster.
* 9m records with 3 fields - first name, last name, email take 45 minutes to run on AWS m5.24xlarge instance with 96 cores, 384 GB RAM
* 80m records with 8-10 fields took less than 2 hours on 1 driver (128 GB RAM, 32 cores), 8 workers (224 GB RAM, 64 cores). This is a user-reported stat without any optimization.
*

```
<img src="https://github.com/user-attachments/assets/4dadeb56-9d66-4ed2-be6e-e0f0b9ab68c6" alt="image" height="1091" width="2271">
```

If you have up to a few million records, it may be easier to run Zingg on a single machine in Spark local mode.


# Zingg Runtime Properties

Memory, external jars and other runtime properties

Zingg jobs can be passed JVM and other settings through a *properties* file. A sample file exists at [config/zingg.conf](https://github.com/zinggAI/zingg/blob/main/config/zingg.conf). The properties can be passed by invoking

`./scripts/zingg.sh --properties-file <location to file> --conf conf.json`

To include jars for Snowflake/BigQuery/MySQL etc, please download them and add them to the `spark.jars` property.


# Zingg Command Line

Using Zingg command line to interact with Zingg

The Zingg command line interface is the way to interact with Zingg to fire *labeling, training,* and *matching* jobs and perform other Zingg functions.

#### To invoke the command line and pass a JSON configuration :

`./scripts/zingg.sh <optional --properties-file path to zingg.conf> --phase <phase> --conf <path to json>`

#### To invoke the command line and run Python programs :

`./scripts/zingg.sh <optional --properties-file path-to-zingg.conf> --run <path-to-python-program>`


# Configuration

JSON configuration file required to work with Zingg

Zingg comes with a command-line script that invokes `spark-submit`. This script needs a **JSON configuration** file to define the input data and match types, location of training data, models, and output.

Sample configuration files are defined at [examples/febrl](https://github.com/zinggAI/zingg/tree/main/examples/febrl) and [examples/febrl120k](https://github.com/zinggAI/zingg/tree/main/examples/febrl120k).

The following sections describe the different JSON attributes of the configuration to get Zingg to work with your data.


# Configuring Through Environment Variables

Passing Configuration value through the system environment variable

If a user does not want to pass the value of any JSON parameter through the config file for security reasons or otherwise, they can configure that value through the *system environment variable*. The system variable name needs to be put in the config file in place of its JSON value. At runtime, the config file gets updated with the value of the environment variable.

Below is the config file snippet that references a few environment variables.

```json
"output" : [{
  "name":"unifiedCustomers", 
  "format":"net.snowflake.spark.snowflake",
  "props": {
    "path": "$location$",
    "delimiter": ",",
    "header": false,				
    "password": "$passwd",					
  }
}],

"labelDataSampleSize" : 0.5,
"numPartitions":4,
"modelId": $modelId$,
"zinggDir": "models",
"collectMetrics": $collectMetrics$
```

Environment variables must be enclosed within dollar signs **`$var$`** to take effect. As usual, `String` variables need to be put *within* quotes **`"$var$"`**, `Boolean` and `Numeric` values should be put *without* quotes **`$var$`**.


# Data Input And Output

Connecting Zingg to user data and writing results to a datastore and format of your choice

This section describes how to configure Zingg to connect to cloud warehouses, file formats, graphs, and database systems.


# Input Data

#### data

An array of input data. Each array entry here refers to a [Zingg Pipe](/latest/connectors/pipes). If the data is self-describing, for e.g. Avro or Parquet, there is no need to define the schema. Else field definitions with names and types need to be provided.

For example, for the CSV under [examples/febrl/test.csv](https://github.com/zinggAI/zingg/tree/0.7.0/examples/febrl/test.csv)

![febrl](https://524808071-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGh6QkdUgeylq0gyuMmBw%2Fuploads%2Fgit-blob-7453fd0787edda97c0f6f38c1f53375d928125b1%2Fimage.png?alt=media)

```python
 "data" : [ {
    "name" : "test",
    "format" : "csv",
    "props" : {
      "delimiter" : ",",
      "header" : "true",
      "path" : "examples/febrl/test.csv"
    },
    "schema" : "{
      \"type\":\"struct\",
      \"fields\":[
      {\"name\":\"id\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},
      {\"name\":\"firstName\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},
      {\"name\":\"lastName\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},
      {\"name\":\"streetnumber\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},
      {\"name\":\"street\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},
      {\"name\":\"address1\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},
      {\"name\":\"address2\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},
      {\"name\":\"areacode\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},
      {\"name\":\"stateCode\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},
      {\"name\":\"dateOfbirth\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},
      {\"name\":\"ssn\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}
      ]
      }"
  }
```

Read more about Zingg Pipes for datastore connections [here](/latest/connectors/pipes).


# Output

Information about output generated

#### output

Definitions are the same as [data](/latest/stepbystep/configuration/data-input-and-output/data) but reflect where you want the Zingg output to get saved.


# Field Definitions

Defining which fields should appear in the output and whether and how they need to be used in matching.

#### fieldDefinition

This is a JSON array representing the fields from the source data to be used for matching, and the kind of matching they need.

Each field denotes a **column** from the input. Fields have the following JSON attributes:

**fieldName**

The **name** of the field from the input data schema.

**fields**

To be defined later. For now, please keep this as the `fieldName`

**dataType**

Type of the column - `string, integer, double`, etc.

**matchType**

* The way to match the given field. Multiple match types can be combined (comma-separated), for example: `FUZZY,NUMERIC`.
* Here are the supported match types and descriptions:

|                        Match Type | Description                                                                                                                                                                                                                                                                                                                    | Applicable To                        |
| --------------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------ |
|                             FUZZY | Broad matches tolerant to typos, abbreviations, and other variations. Uses fuzzy string similarity features.                                                                                                                                                                                                                   | string, integer, long, double, date  |
|                  FUZZY\_OPTIMISED | Same semantics as FUZZY but uses an optimized implementation. Provides similar matching quality with significantly lower CPU and memory usage on large datasets. Recommended for production/large-scale matching when FUZZY quality is desired but performance is critical. [Zingg Enterprise Feature](#user-content-fn-1)[^1] | string, integer, long, double, date  |
|                             EXACT | Exact match with no tolerance for variation. Preferable for country codes, pin codes, and other categorical variables where you expect no variations.                                                                                                                                                                          | string, integer, long, date, boolean |
|                         DONT\_USE | Included in the output but no computation is done on this field. Useful for IDs required in the output. DONT\_USE fields are not shown to the labeler when [showConcise](https://github.com/zinggAI/zingg/tree/0.7.0/docs/stepbystep/configuration/label.md) is set to true.                                                   | any                                  |
|                             EMAIL | Matches only the local part (before the @) of email addresses.                                                                                                                                                                                                                                                                 | string                               |
|                  EMAIL\_OPTIMISED | Same semantics as EMAIL but uses an optimized implementation for much faster evaluation on large datasets while preserving match behavior. Recommended for large-scale runs where many emails are compared. [Zingg Enterprise Feature](#user-content-fn-1)[^1]                                                                 | string                               |
|                           PINCODE | Matches postal / pin codes (supports typical local formats such as `xxxxx` or `xxxxx-xxxx` depending on data).                                                                                                                                                                                                                 | string                               |
|                   NULL\_OR\_BLANK | By default, Zingg treats nulls as matches. If NULL\_OR\_BLANK is added to a field that also has other match types (e.g., `FUZZY`), Zingg will build an explicit feature for null/blank values so the model can learn their effect.                                                                                             | string, integer, long, date, boolean |
|                              TEXT | Compares overlapping words between two strings. Good for descriptive or long-text fields without many typos.                                                                                                                                                                                                                   | string                               |
|                           NUMERIC | Extracts numbers from strings and compares how many are the same across both strings (useful for apartment numbers, building numbers, etc.).                                                                                                                                                                                   | string                               |
|              NUMERIC\_WITH\_UNITS | Extracts product codes or numbers with units (e.g., `16gb`) and compares how many are same across both strings                                                                                                                                                                                                                 | string                               |
|            ONLY\_ALPHABETS\_EXACT | Compares only the alphabetic characters and requires an exact match. Useful when numeric parts should be ignored (e.g., building name vs flat number).                                                                                                                                                                         | string                               |
|            ONLY\_ALPHABETS\_FUZZY | Compares only the alphabetic characters using a fuzzy comparison; numeric characters are ignored. Useful for addresses when you want to handle street names fuzzily while numeric parts are handled separately (e.g., via NUMERIC).                                                                                            | string                               |
| ONLY\_ALPHABETS\_FUZZY\_OPTIMISED | Same semantics as ONLY\_ALPHABETS\_FUZZY but uses optimized processing. Use this when you need the fuzzy alphabet-only behavior at production scale. [Zingg Enterprise Feature](#user-content-fn-1)[^1]                                                                                                                        | string                               |
|               MAPPING\_(FILENAME) | Maps input values to canonical values using a mapping file (e.g., nickname maps, company abbreviations, gender codes). Matching is tolerant to common variations defined in the mapping. See [Advanced Match Types](/latest/stepbystep/configuration/adv-matchtypes) for mapping file format and examples.                     | string                               |

[^1]: Zingg Enterprise is the suite of proprietary products licensed by Zingg. Please refer to <https://www.zingg.ai/product/zingg-entity-resolution-compare-versions> for individual tier features.


# Standardise Postprocessor

## Overview

The Standardise Postprocessor standardises field values in matched output using predefined mappings. It addresses inconsistent values that commonly appear across systems (for example, job titles such as "VP of Ops", "Vice President Operations", or "V.P. Operations").

Key points:

* The postprocessor runs after matching and before writing output.
* It does not affect matching behavior; it transforms output values only.
* Matching for mapping lookup is case-insensitive by default.
* Primary key fields are never postprocessed.

Industry usage and benefits:

* Human resources: standardise job titles and roles across multiple systems to improve headcount and reporting accuracy.
* Sales and CRM: normalize company names for consistent account consolidation and analytics.
* Product and catalog management: unify legacy and new product codes for inventory and billing reconciliation.
* Compliance and reporting: enforce canonical values to simplify regulatory reporting and downstream processing.

***

## Mapping file format and rules

Create a JSON file where each element is an array of equivalent values. The **first value** in each array is the canonical value and is used as the replacement for all other values in that row.

Important rules:

* First value is canonical: e.g. `["Chief Executive Officer", "CEO", "Executive Director"]` — all variants map to `Chief Executive Officer`.
* Lookup is case-insensitive: `CEO`, `ceo`, `Ceo` will match.
* Rows must be disjoint: a string must not appear in two different canonical groups.
* The processor does not normalize extra whitespace, punctuation, or partial matches. Those are not handled automatically.

Example: `jobtitles.json`

```json
[
  ["Chief Executive Officer", "CEO", "Executive Director", "Managing Director", "President"],
  ["Vice President of Operations", "VP of Ops", "Vice President Operations", "V.P. Operations", "VP Ops"],
  ["Software Engineer", "SWE", "Software Developer", "Developer", "Programmer"],
  ["Data Scientist", "Data Science Engineer", "DS Engineer", "ML Engineer"],
  ["Senior Manager", "Sr Manager", "Sr. Manager", "Senior Mgr"]
]
```

Place mapping files from where you are executing. Use the base filename (without `.json`) when referencing the mapping.

***

## Configure postprocessing

Here is how to configure the `StandardisePostprocessor` for a field in Python:

```python
fname = EFieldDefinition("fname", "string", MatchType.FUZZY)
fname.setPostProcessors([StandardisePostprocessorType("STANDARDISE", "nicknames_test")])
```

## Python example

```python
from zingg.client import *
from zinggEC.enterprise.common.EArguments import *
from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition
from zinggEC.enterprise.common.StandardisePostprocessorType import StandardisePostprocessorType
from zinggEC.enterprise.common.MappingMatchType import MappingMatchType
from zinggEC.enterprise.common.epipes import *
from zinggES.enterprise.spark.ESparkClient import *
from zinggEC.enterprise.common.EClientOptions import *

args = EArguments()

id = EFieldDefinition("id", "string", MatchType.DONT_USE)
id.setPrimaryKey(True)

# Example: Here we are using postprocessing for job_title
job_title = EFieldDefinition("job_title", "string", MatchType.FUZZY)
job_title.setPostProcessors([StandardisePostprocessorType("STANDARDISE", "jobtitles")])

fieldDefs = [id, job_title]
args.setFieldDefinition(fieldDefs)

# configure input/output pipes and run as usual
```

***

## JSON configuration example

Use `STANDARDISE_<basename>` to reference a mapping file named `<basename>.json`.

```json
{
  "fieldDefinition": [
    {
      "fieldName": "id",
      "matchType": "dont_use",
      "dataType": "string",
      "primaryKey": true
    },
    {
      "fieldName": "job_title",
      "matchType": "fuzzy",
      "dataType": "string",
      "postProcessors": "STANDARDISE_jobtitles"
    }
  ]
}
```


# User Defined Mapping Match Types

Defining match types to match better and faster

[Zingg Enterprise Feature](#user-content-fn-1)[^1]

**Leveraging domain expertise to push matching accuracy. Also save time massaging data before matching.**

While the Zingg matching algorithms learn the variations from the user labels, completely different attributes pose a challenge to matching accuracy. In the case of nicknames, or company abberviations, domain input can greatly enhance matching accuracy. Zingg's `MAPPING` match type is built for such cases.

To leverage user expertise for nicknames for example, the user can supply Zingg with a mapping json file `nicknames.json` which denotes nicknames. Zingg has prebuilt mappings for company names, nicknames etc. Or you can define your own mappings too.

Here is the structure of the json:

<pre class="language-json"><code class="lang-json"><strong>[  
</strong>  ["Will", "Bill", "William"],
  ["John", "Johnny", "Jack"],
  ["Robert", "Rob", "Bob", "Bobby"],
  ["Charles", "Charlie", "Chuck"],
  ["James", "Jim", "Jimmy"],
  ["Thomas", "Tom", "Tommy"]
...
]   
</code></pre>

Each line here represents common nicknames which represent the same name.

To use this mapping within Zingg, define the field's match type as `MAPPING_<filename>` which in our case would be `mapping_nicknames`

```json
"fieldDefinition":[
   	{
   		"fieldName" : "name",
   		"matchType" : "mapping_nicknames, fuzzy",
   		"fields" : "name",
   		"dataType": "string"
   	}
```

### Transform and Match

The `MAPPING` match type can also be used to transform and normalise categorical data. Let us say different data sources have different representations of gender. In one, gender is represented as M and F, in another it is noted as Male, Female and in the third as 1 and 2. Instead of transforming the gender column beforehand, one could create a mapping json called `gender.json` .

<pre><code><strong>[  
</strong>  ["M", "Male", "1"],
  ["F", "Female", "2"]
]  
</code></pre>

When we use it for the gender field like below, Zingg would automatically handle the transformation so that you don't have to.

```
"fieldDefinition":[
   	{
   		"fieldName" : "gender",
   		"matchType" : "mapping_gender, exact",
   		"fields" : "gender",
   		"dataType": "string"
   	},
```

[^1]: Zingg Enterprise is the suite of proprietary products licensed by Zingg. Please refer to <https://www.zingg.ai/product/zingg-entity-resolution-compare-versions> for individual tier features.


# Deterministic Matching

Ensuring higher matching accuracy and performance

[Zingg Enterprise Feature](#user-content-fn-1)[^1]

Zingg Enterprise allows the ability to plug rule-based [deterministic matching](https://www.learningfromdata.zingg.ai/p/deterministic-matching-vs-probabilistic) along with already Zingg AI's probabilistic matching. If the data contains *sure* identifiers like emails, SSNs, passport-ids etc, we can use these attributes to resolve records.

The deterministic matching flow is weaved into Zingg's flow to ensure that each record which has a match finds one, probabilistically, deterministically or both. If the data has known identifiers, Zingg Enterprise's Deterministic Matching highly improves both matching accuracy and performance.

### Example For Configuring In JSON:

```json
    "deterministicMatching":[  
        {  
           "matchCondition":[{"fieldName":"fname"},{"fieldName":"stNo"},{"fieldName":"add1"}]  
        },  
        {  
           "matchCondition":[{"fieldName":"fname"},{"fieldName":"dob"},{"fieldName":"ssn"}]  
        },   
        {  
           "matchCondition":[{"fieldName":"fname"},{"fieldName":"email"}]  
        }  
    ]  
```

#### Python Code Example:

```{python}
detMatchNameAdd = DeterministicMatching('fname','stNo','add1')  
detMatchNameDobSsn = DeterministicMatching('fname','dob','ssn')  
detMatchNameEmail = DeterministicMatching('fname','email')  
args.setDeterministicMatchingCondition(detMatchNameAdd,detMatchNameDobSsn,detMatchNameEmail)  
```

#### How Will It Work:

The above conditions would translate into the following:

1. Those rows which have **exactly** same `fname`, `stNo` and `add1` => exact match with max score 1\
   \&#xNAN;*OR*
2. Those rows which have **exactly** same `fname`, `dob` and `ssn` => exact match with max score 1\
   \&#xNAN;*OR*
3. Those rows which have **exactly** same `fname` and `email` => exact match with max score 1

[^1]: Zingg Enterprise is an advance version of Zingg Community with production grade features


# Pass Through Data

Supressing records entirely during matching yet preserving them in the output

[Zingg Enterprise Feature](#user-content-fn-1)[^1]

In many cases, our input consists for records which are partially populated, outdated or not fit for matching in some way or other. One likely cause for such data is a source system with poor quality information. For governance and compliance purposes, we still want these records in the identity graph, but we do not want them to play a role in matching.

Pass Through Configuration allows such records to appear in the Zingg output but be ignored during matching, linking and incremental processes. Each pass through record gets a unique ZINGG\_ID

### Example For Configuring In JSON:

```json
     "passthroughExpr": "is_deceased = true"
```

Note: Zingg internally applies the **negation** of the `passthroughExpr` to filter matching records. If the passthrough condition is being applied on nullable fields, please ensure that the negative of the passthrough expression yields the records which are NOT passthrough. For example:

```json
     "passthroughExpr": "is_deceased = true AND is_deceased is NOT NULL"
```

[^1]: Zingg Enterprise is the suite of proprietary products licensed by Zingg. Please refer to <https://www.zingg.ai/product/zingg-entity-resolution-compare-versions> for individual tier features.


# Model Location

#### zinggDir

The **location** where trained models will be saved. Defaults to `/tmp/zingg`

#### modelId

An **identifier** for the model. You can train multiple models - say, one for **customers** matching *names*, *age*, and other personal details and one for **households** matching *addresses*. Each model gets saved under `zinggDir/modelId`


# Tuning Label, Match And Link Jobs

#### numPartitions

The number of Spark partitions over which the input data is distributed. Keep it equal to 20-30 times the number of cores. This is an important configuration for performance.

#### labelDataSampleSize

Fraction of the data to be used for training the models. Adjust it between 0.0001 and 0.1 to keep the sample size small enough so that it finds enough edge cases fast. If the size is bigger, the `findTrainingData` job will spend more time combing through samples. If the size is too small, Zingg may not find the right edge cases.


# Telemetry

Product usage data captured by Zingg OSS to improve user experience

#### collectMetrics

Application captures a few measurements for runtime metrics such as *no. of data records, no. of features, running phase,* and a few more.

**Zingg does not capture any user data or input data and will never do so.**

This feature may be disabled by setting this flag to **false**. The default value is **true**. For details, refer to [Security And Privacy](/latest/security).


# Working With Training Data

Zingg builds models to predict *similarity*. Training data is needed to build these models. The next sections describe how you can use the **Zingg Interactive Labeler** to create the training data.


# Finding Records For Training Set Creation

Pairs of records that match or dont so as to train Zingg

The **findTrainingData** phase prompts Zingg to search for edge cases in the data which can be labeled by the user and used for learning. During this phase, Zingg combs through the data samples and judiciously selects limited representative pairs that can be labelled by the user. Zingg is very frugal about the training so that user effort is minimized and models can be built and deployed quickly.

This **findTrainingData** job writes the edge cases to the folder configured through `zinggDir/modelId` in the config:

`./zingg.sh --phase findTrainingData --conf config.json`

The **findTrainingData** phase is run first and then the label phase is run and this cycle is repeated so that the Zingg models get smarter from user feedback.


# Labeling Records

Providing user feedback on the training pairs

The label phase opens an *interactive* learner where the user can mark the pairs found by **findTrainingData** phase as matches or non-matches. The **findTrainingData** phase generates edge cases for labeling and the label phase helps the user mark them.

`./zingg.sh --phase label --conf config.json <optional --showConcise=true|false>`

Proceed running **findTrainingData** followed by label phases till you have at least 30-40 positives, and when you see the predictions by Zingg converging with the output you want. At each stage, the user will get different variations of attributes across the records. Zingg performs pretty well with even a small number of training, as the samples to be labeled are chosen by the algorithm itself.

**How much training is enough**

The labelled data is the real configuration for Zingg, through which the different Zingg models get created. Heuristically, 30-40 matching records are a good start before you train and match. Actual numbers vary on the size of data, number of attributes, cluster sizes and performance needs. More training, like going to 500-1000 matches, will surely help with performance on almost any dataset. Point to remember here is - Is the effort worth it :smile:

![Shows records and asks user to mark yes, no, can't say on the cli.](https://524808071-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGh6QkdUgeylq0gyuMmBw%2Fuploads%2Fgit-blob-cf3181376aa6b90efb764bbe1f4340e2c210edf4%2Flabelvertical.gif?alt=media)

The **showConcise** flag when passed to the Zingg command line only shows fields which are **NOT DONT\_USE**


# Find And Label

This phase is composed of two phases namely [findTrainingData](/latest/stepbystep/createtrainingdata/findtrainingdata) and [label](/latest/stepbystep/createtrainingdata/label). This will help experienced users to quicken the process of creating training data.

`./zingg.sh --phase findAndLabel --conf config.json`

As this is phase runs **findTrainingData** and **label** together, it should be run only for small datasets where **findTrainingData** takes a short time to run, else the the user will have to wait long for the console to label.


# Using Pre-existing Training Data

Supplementing Zingg With Existing Training Data

If you already have some training data that you want to start with, you can use that as well with Zingg. Add an attribute **trainingSamples** to the config and define the training pairs.

The training data supplied to Zingg should have a **z\_cluster** column that groups the records together. The **z\_cluster** uniquely identifies the group. We also need to add the **z\_isMatch** column which is **1** if the pairs *match* or **0** if they do *not* match. The **z\_isMatch** value has to be the same for all the records in the **z\_cluster** group. They either match with each other or they don't.

An example is provided in [GitHub training data](https://github.com/zinggAI/zingg/tree/0.7.0/examples/febrl/training.csv).\
Here, the first column specifies the z\_cluster, the second column specifies the z\_isMatch value and the remaining columns are the ones which are used for training the model.

The above training data can be specified using [trainingSamples attribute in the configuration.](https://github.com/zinggAI/zingg/tree/0.7.0/examples/febrl/configWithTrainingSamples.json)

**Note**: It is advisable to still run [findTrainingData](/latest/stepbystep/createtrainingdata/findtrainingdata) and [label](/latest/stepbystep/createtrainingdata/label) a few rounds to tune Zingg with the supplied training data as well as patterns it needs to learn independently.


# Updating Labeled Pairs

N**ote: Please keep a backup copy of your model folder in a separate place before running this**

As our understanding of our data changes, we may need to revisit the previously marked pairs and update them. To do this, please [generate the documentation of the model.](/latest/stepbystep/createtrainingdata/generatingdocumentation)

You can then invoke the updater by invoking\
`./scripts/zingg.sh --phase updateLabel --conf <location to conf.json>`

This brings up the console labeler which accepts the **cluster id** of the pairs you want to update.

![Shows records and asks user to update yes, no, can't say on the CLI.](https://524808071-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGh6QkdUgeylq0gyuMmBw%2Fuploads%2Fgit-blob-cf64435c1417dc6d6b643c3d43b631e048f6517c%2Fupdatelabelvertical.gif?alt=media)


# Documenting The Training Data

We may need to send our labeled data for a subject matter expert to review. We may also like to understand and analyse our training data. In such scenarios, we can document the records we have marked during the **label** phase by invoking the **generateDocs** phase.

During **generateDocs**, Zingg generates readable documentation about the training data, including those marked as matches, as well as non-matches. The documentation is written to the **zinggDir/modelId** folder and can be built using the following

```
./scripts/zingg.sh --phase generateDocs --conf <location to conf.json> <optional --showConcise=true|false>
```

The generated documentation file can be viewed in a browser and looks like as below:

![Training Data](https://524808071-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGh6QkdUgeylq0gyuMmBw%2Fuploads%2Fgit-blob-43299765c1ed12c1bc3694f7f91fdc9d2f8bea0e%2Fdocumentation.png?alt=media)


# Model Difference

Comparison of two outputs with different models

[Zingg Enterprise Feature](#user-content-fn-1)[^1]

## Understand Your Model Changes with Precision

When you evolve your Zingg model—changing field definitions from fuzzy to exact, adding new match types, introducing deterministic matching rules, or tuning blocking strategies—you need to understand the impact. The **diff** phase provides a detailed comparison between two model outputs, showing you exactly which records and clusters were affected by your changes. Make data-driven decisions about which model performs better for your use case.

## Why Use Diff?

* **Model Comparison**: Understand the impact of changing field types (fuzzy → exact, adding new match types)
* **Validation & Testing**: Verify that model improvements actually produce better results
* **Impact Analysis**: See which specific records and clusters changed between model versions
* **Confidence Building**: Make informed decisions about deploying new models to production
* **A/B Testing**: Compare different model configurations to find the optimal setup
* **Regression Detection**: Ensure new model changes don't degrade match quality
* **Cluster Evolution**: Track how clusters merge, split, or change as your model evolves

## How It Works

The diff phase compares two configurations:

1. **Original Configuration**: Your baseline model (e.g., with fuzzy matching)
2. **New Configuration**: Your updated model (e.g., with exact matching or new rules)

The diff phase analyzes both outputs and identifies:

* **Records that moved between clusters** due to model changes
* **New clusters** created in the updated model
* **Merged or split clusters** between the two models

## Usage

### Command Line

```bash
./scripts/zingg.sh --phase diff \
  --conf examples/febrl/sparkIncremental/configdiff.json \
  --compareTo examples/febrl/configBaseline.json \
  --properties-file config/zingg.conf
```

**Key Parameters:**

* `--conf`: Points to your **new/updated configuration** (the model you want to evaluate)
* `--compareTo`: Points to your **original/baseline configuration** (the model you're comparing against)
* `--properties-file`: Zingg properties file (optional)

### Python API

Python users can pass configuration objects directly instead of JSON files. See the complete example in `diffExample.py` below.

```python
from zingg.client import *
from zingg.pipes import *
from zinggEC.enterprise.common.EArguments import *
from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition
from zinggES.enterprise.spark.ESparkClient import *
from zinggEC.enterprise.common.TransformedOutputArguments import *
from zinggEC.enterprise.common.EClientOptions import *

# Step 1: Define your ORIGINAL/BASELINE configuration
# This is the model you're comparing against (e.g., with fuzzy matching)
originalArgs = EArguments()
id = EFieldDefinition("id", "string", MatchType.DONT_USE)
id.setPrimaryKey(True)
fname = EFieldDefinition("fname", "string", MatchType.FUZZY)
lname = EFieldDefinition("lname", "string", MatchType.FUZZY)
stNo = EFieldDefinition("stNo", "string", MatchType.FUZZY)
add1 = EFieldDefinition("add1", "string", MatchType.FUZZY)
add2 = EFieldDefinition("add2", "string", MatchType.FUZZY)
city = EFieldDefinition("city", "string", MatchType.FUZZY)
areacode = EFieldDefinition("areacode", "string", MatchType.FUZZY)
state = EFieldDefinition("state", "string", MatchType.FUZZY)
dob = EFieldDefinition("dob", "string", MatchType.FUZZY)
ssn = EFieldDefinition("ssn", "string", MatchType.FUZZY)

originalArgs.setFieldDefinition([id, fname, lname, stNo, add1, add2, city, areacode, state, dob, ssn])
originalArgs.setModelId("100")
originalArgs.setZinggDir("./models")
originalArgs.setNumPartitions(4)

schema = "id string, fname string, lname string, stNo string, add1 string, add2 string, city string, areacode string, state string, dob string, ssn string"
originalInputPipe = ECsvPipe("originalData", "examples/febrl/test.csv", schema)
originalArgs.setData(originalInputPipe)
originalOutputPipe = ECsvPipe("originalOutput", "/tmp/zinggOutputOriginal")
originalOutputPipe.setHeader("true")
originalArgs.setOutput(originalOutputPipe)

# IMPORTANT: trainMatch phase must already be completed for the original configuration
# The diff phase reads the output from the original model

# Step 2: Define your NEW/UPDATED configuration
# Example: Changed some fields from FUZZY to EXACT, added deterministic matching
newArgs = EArguments()
id = EFieldDefinition("id", "string", MatchType.DONT_USE)
id.setPrimaryKey(True)
fname = EFieldDefinition("fname", "string", MatchType.FUZZY)
lname = EFieldDefinition("lname", "string", MatchType.FUZZY)
stNo = EFieldDefinition("stNo", "string", MatchType.EXACT)      # Changed from FUZZY
add1 = EFieldDefinition("add1", "string", MatchType.FUZZY)
add2 = EFieldDefinition("add2", "string", MatchType.FUZZY)
city = EFieldDefinition("city", "string", MatchType.EXACT)      # Changed from FUZZY
areacode = EFieldDefinition("areacode", "string", MatchType.EXACT)  # Changed from FUZZY
state = EFieldDefinition("state", "string", MatchType.EXACT)    # Changed from FUZZY
dob = EFieldDefinition("dob", "string", MatchType.EXACT)        # Changed from FUZZY
ssn = EFieldDefinition("ssn", "string", MatchType.FUZZY)

newArgs.setFieldDefinition([id, fname, lname, stNo, add1, add2, city, areacode, state, dob, ssn])
newArgs.setModelId("999")
newArgs.setZinggDir("./models")
newArgs.setNumPartitions(4)

# Added deterministic matching rules
dm1 = DeterministicMatching('fname', 'stNo', 'add1')
dm2 = DeterministicMatching('ssn')
newArgs.setDeterministicMatchingCondition(dm1, dm2)

newInputPipe = ECsvPipe("newData", "examples/febrl/test.csv", schema)
newArgs.setData(newInputPipe)
newOutputPipe = ECsvPipe("newOutput", "/tmp/zinggOutputNew")
newOutputPipe.setHeader("true")
newArgs.setOutput(newOutputPipe)

# IMPORTANT: Run trainMatch on the new configuration first
options = EClientOptions([EClientOptions.PHASE, "trainMatch"])
zinggNew = EZingg(newArgs, options)
zinggNew.initAndExecute()

# Step 3: Create TransformedOutputArguments for diff
diffArgs = TransformedOutputArguments()
diffArgs.setParentArgs(newArgs)        # New configuration
diffArgs.setCompareToArgs(originalArgs) # Original configuration to compare against

# Set output path for diff results
diffOutputPipe = ECsvPipe("diffOutput", "/tmp/zinggDiff")
diffOutputPipe.setHeader("true")
diffArgs.setTransformedOutputPath(diffOutputPipe)

# Step 4: Execute diff phase
diffOptions = EClientOptions([EClientOptions.PHASE, "diff"])
zinggDiff = EZingg(diffArgs, diffOptions)
zinggDiff.initAndExecute()
```

## Configuration Files

### Configuration Wrapper (`examples/febrl/sparkIncremental/configdiff.json`)

This wrapper configuration points to your new configuration and specifies where to write the diff output:

```json
{
    "config": "$ZINGG_ENT_REPO$/spark/examples/febrl/configdiffUpdated.json",
    "transformedOutputPath": {
        "name": "diffOutput",
        "format": "csv",
        "props": {
            "location": "/tmp/zinggTransformedOutputDiff",
            "delimiter": ",",
            "header": true
        }
    }
}
```

**Note**:

* This wrapper only points to the new configuration. The original/baseline configuration is specified via the `--compareTo` command-line parameter.
* The `name` field in `transformedOutputPath` can be any arbitrary identifier for the output pipe - it's used internally by Zingg to identify this output destination. Here we use `diffOutput` to clearly distinguish it from the new model's match output.

### New Configuration (`examples/febrl/configdiffUpdated.json`)

Your updated model configuration with changes—for example, switching some fields from `fuzzy` to `exact` matching:

```json
{
  "fieldDefinition": [
    {
      "fieldName": "id",
      "matchType": "dont_use",
      "fields": "id",
      "dataType": "string",
      "primaryKey": true
    },
    {
      "fieldName": "fname",
      "matchType": "fuzzy",
      "fields": "fname",
      "dataType": "string"
    },
    {
      "fieldName": "lname",
      "matchType": "fuzzy",
      "fields": "lname",
      "dataType": "string"
    },
    {
      "fieldName": "stNo",
      "matchType": "exact",
      "fields": "stNo",
      "dataType": "string"
    },
    {
      "fieldName": "add1",
      "matchType": "fuzzy",
      "fields": "add1",
      "dataType": "string"
    },
    {
      "fieldName": "city",
      "matchType": "exact",
      "fields": "city",
      "dataType": "string"
    },
    {
      "fieldName": "areacode",
      "matchType": "exact",
      "fields": "areacode",
      "dataType": "string"
    },
    {
      "fieldName": "state",
      "matchType": "exact",
      "fields": "state",
      "dataType": "string"
    },
    {
      "fieldName": "dob",
      "matchType": "exact",
      "fields": "dob",
      "dataType": "string"
    }
  ],
  "output": [{
    "name": "OUTPUT_FEBRL",
    "format": "csv",
    "props": {
      "location": "/tmp/zinggOutputNew/",
      "delimiter": ",",
      "header": true
    }
  }],
  "data": [{
    "name": "test",
    "format": "csv",
    "props": {
      "location": "examples/febrl/testmodeldiff.csv",
      "delimiter": ",",
      "header": false
    },
    "schema": "id string, fname string, lname string, stNo string, add1 string, add2 string, city string, state string, areacode string, dob string, ssn string"
  }],
  "deterministicMatching": [
    {
      "matchCondition": [
        {"fieldName": "fname"},
        {"fieldName": "stNo"},
        {"fieldName": "add1"}
      ]
    },
    {
      "matchCondition": [
        {"fieldName": "ssn"}
      ]
    }
  ],
  "outputStats": {
    "name": "stats",
    "format": "csv",
    "props": {
      "location": "/tmp/zinggStats_$ZINGG_DYNAMIC_STAT_NAME",
      "delimiter": ",",
      "header": true
    }
  },
  "labelDataSampleSize": 0.5,
  "numPartitions": 4,
  "modelId": 999,
  "zinggDir": "models"
}
```

### Original Configuration (`examples/febrl/configBaseline.json`)

Your baseline model configuration (the one you're comparing against):

```json
{
  "fieldDefinition": [
    {
      "fieldName": "id",
      "matchType": "dont_use",
      "fields": "id",
      "dataType": "string",
      "primaryKey": true
    },
    {
      "fieldName": "fname",
      "matchType": "fuzzy",
      "fields": "fname",
      "dataType": "string"
    },
    {
      "fieldName": "lname",
      "matchType": "fuzzy",
      "fields": "lname",
      "dataType": "string"
    },
    {
      "fieldName": "stNo",
      "matchType": "fuzzy",
      "fields": "stNo",
      "dataType": "string"
    },
    {
      "fieldName": "add1",
      "matchType": "fuzzy",
      "fields": "add1",
      "dataType": "string"
    },
    {
      "fieldName": "add2",
      "matchType": "fuzzy",
      "fields": "add2",
      "dataType": "string"
    },
    {
      "fieldName": "city",
      "matchType": "fuzzy",
      "fields": "city",
      "dataType": "string"
    },
    {
      "fieldName": "areacode",
      "matchType": "fuzzy",
      "fields": "areacode",
      "dataType": "string"
    },
    {
      "fieldName": "state",
      "matchType": "fuzzy",
      "fields": "state",
      "dataType": "string"
    },
    {
      "fieldName": "dob",
      "matchType": "fuzzy",
      "fields": "dob",
      "dataType": "string"
    },
    {
      "fieldName": "ssn",
      "matchType": "fuzzy",
      "fields": "ssn",
      "dataType": "string"
    }
  ],
  "output": [{
    "name": "output",
    "format": "csv",
    "props": {
      "location": "/tmp/zinggOutputOriginal",
      "delimiter": ",",
      "header": true
    }
  }],
  "data": [{
    "name": "test",
    "format": "csv",
    "props": {
      "location": "examples/febrl/testmodeldiff.csv",
      "delimiter": ",",
      "header": false
    },
    "schema": "id string, fname string, lname string, stNo string, add1 string, add2 string, city string, state string, areacode string, dob string, ssn string"
  }],
  "outputStats": {
    "name": "stats",
    "format": "csv",
    "props": {
      "location": "/tmp/zinggStats_$ZINGG_DYNAMIC_STAT_NAME",
      "delimiter": ",",
      "header": true
    }
  },
  "labelDataSampleSize": 0.5,
  "numPartitions": 4,
  "modelId": 100,
  "zinggDir": "models"
}
```

## Output Format

The diff output contains **only the records that were impacted** by the model changes, making it easy to focus on what actually changed.

### Output Fields

The diff output includes:

* **Primary Keys of records** from both configurations
* **ZINGG\_ID\_UPDATED**: ZINGG ID from the new/updated model
* **ZINGG\_ID\_ORIGINAL**: ZINGG ID from the original/baseline model

This allows you to see side-by-side how each record's cluster assignment changed between the two models.

## Enhanced Diff Output with Outer Join

Coming Soon. We're developing an enhanced diff that will provide even more comprehensive comparison capabilities. This enhanced diff will include:

* **All records from both models**, including those that appear in only one output
* **Enhanced null handling**: Intelligent merging of primary key columns when records appear in only one model

## Use Cases

### 1. Field Type Changes

Compare fuzzy vs. exact matching to find the right balance between precision and recall.

### 2. Deterministic Matching Evaluation

See how adding or changing deterministic matching rules affects your clusters.

### 3. Blocking Strategy Comparison

Compare DEFAULT vs. WIDER blocking strategies to understand the performance/accuracy trade-off.

### 4. Model Validation

Validate that model improvements (nicknames, better classifiers, etc.) actually improve results.

### 5. Regression Testing

Ensure that model updates don't accidentally degrade match quality for specific data patterns.

### 6. Configuration Tuning

Experiment with different labelDataSampleSize, numPartitions, or other parameters and see the impact.

[^1]: Zingg Enterprise is the suite of proprietary products licensed by Zingg. Please refer to <https://www.zingg.ai/product/zingg-entity-resolution-compare-versions> for individual tier features.


# Ensuring Scalability

Understanding how blocking is working before running match or link

[Zingg Enterprise Feature](#user-content-fn-1)[^1]

The [Blocking Model](/latest/zmodels) ensures that Zingg stays performant by learning field heuristics and reducing the number of comparisons through the training data. A poorly learnt blocking model will cause Zingg matching jobs to become slow or not complete entirely. This can happen due to a variety of reasons like:

* A user adds significantly larger training samples compared to the labelling learnt by Zingg. The manually added training samples may have the same type of columns and blocking rules learnt are not generic enough. For example, providing California state only training data when the matching is using the State column and data has multiple states.
* When there is a natural bias in the data with lots of null columns used in matching.
* When sufficient labeling has not been done.
* When there a lot of non differentiating columns.

Matching is computationally expensive, and If we can have an understanding of how blocking is working, we can decide whether we need to add more training data. The **verifyBlocking** phase lets us check if we have collected sufficient labels and have a good blocking model.

### The verifyBlocking phase is run as follows:

`./scripts/zingg.sh --phase verifyBlocking --conf <path to conf> <optional --zinggDir <location of model>>`

The output contains two directories -

`zinggDir/modelId/blocks/timestamp/counts` `zinggDir/modelId/blocks/timestamp/blockSamples`

We can see the counts per block and the top 10% records associated with all the blocks.

For **Zingg Enterprise for Snowflake**, verifyBlocking generates tables with the names:

`zingg_modelId_blocks_timestamp_counts` where we can see the counts per block and `zingg_modelId_blocks_timestamp_blockSamples_hash` where we can see the records associated with the blocks.

[^1]: Zingg Enterprise is the suite of proprietary products licensed by Zingg. Please refer to <https://www.zingg.ai/product/zingg-entity-resolution-compare-versions> for individual tier features.


# Building And Saving The Model

So that the same model can be applied to new data

Builds up the Zingg models using the training data from the above phases and writes them to the folder **zinggDir/modelId** as specified in the config.

```
./zingg.sh --phase train --conf config.json
```


# Finding The Matches

Finds the records that match with each other.

`./zingg.sh --phase match --conf config.json`

As can be seen in the image below, matching records are given the same **z\_cluster** id/Zingg ID in the case of Zingg Enterprise. Each record also gets a **z\_minScore** and **z\_maxScore** which shows the *least/greatest* it matched with other records in the same cluster.

![Match results](https://524808071-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGh6QkdUgeylq0gyuMmBw%2Fuploads%2Fgit-blob-fa7133f1ce5a32b044f2094de87fc33183965d38%2Fmatch.gif?alt=media)

If records across multiple sources have to be matched, the [link phase](/latest/stepbystep/link) should be used.


# Adding Incremental Data

Building a continuously updated identity graph with new, updated, and deleted records

[Zingg Enterprise Feature](#user-content-fn-1)[^1]

Rerunning matching on entire datasets is wasteful, and we lose the lineage of matched records against a persistent identifier. Using the [incremental flow](https://www.learningfromdata.zingg.ai/p/zingg-incremental-flow) feature in [Zingg Enterprise](https://www.zingg.ai), incremental loads can be run to match existing pre-resolved entities. The new and updated records are matched to existing clusters, and new persistent [**ZINGG\_IDs**](https://www.learningfromdata.zingg.ai/p/hello-zingg-id) are generated for records that do not find a match.

If a record gets updated and Zingg Enterprise discovers that it is a more suitable match with another cluster, it will be reassigned. Cluster assignment, merge, and unmerge happens automatically in the flow. Zingg Enterprise also takes care of human feedback on previously matched data to ensure that it does not override the approved records.

### The incremental phase is run as follows:

`./scripts/zingg.sh --phase runIncremental --conf <location to incrementalConf.json> <optional --properties-file path to zingg.conf>`

### Example incrementalConf.json:

The outputTmp section in the incremental configuration specifies a temporary output location for the results generated during the incremental matching process. This is where Zingg writes the intermediate or temporary results before final processing or merging with the main output.

```json
{      
    "config" : "config.json",  
    "incrementalData": [{  
            "name":"customers_incr",   
            "format":"csv",   
            "props": {  
                "path": "test-incr.csv",  
                "delimiter": ",",  
                "header":false  
            },  
            "schema": "recId string, fname string, lname string, stNo string, add1 string, add2 string, city string, state string, areacode string, dob string, ssn  string"   
        }  
    ],
    "outputTmp" :{
            "name":"customers_incr_temp",
            "format":"csv",
            "props": {
              "location": "/tmp/zinggOutput_febrl_tmp",
              "delimiter": ",",
              "header":true
            }
    }   
}  
```

### runIncremental can also be triggered using Python by invoking:

`./scripts/zingg.sh --run examples/FebrlExample.py`

#### Python Code Example:

```{python}
#import the packages  
  
from zingg.client import *  
from zingg.pipes import *  
from zinggEC.enterprise.common.ApproverArguments import *  
from zinggEC.enterprise.common.IncrementalArguments import *  
from zinggEC.enterprise.common.epipes import *  
from zinggEC.enterprise.common.EArguments import *  
from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition  
from zinggES.enterprise.spark.ESparkClient import *  
import os  
  
#build the arguments for zingg  
args = EArguments()  
#set field definitions  
recId = EFieldDefinition("recId", "string", MatchType.DONT_USE)  
recId.setPrimaryKey(True)  
fname = EFieldDefinition("fname", "string", MatchType.FUZZY)  
lname = EFieldDefinition("lname", "string", MatchType.FUZZY)  
stNo = EFieldDefinition("stNo", "string", MatchType.FUZZY)  
add1 = EFieldDefinition("add1","string", MatchType.FUZZY)  
add2 = EFieldDefinition("add2", "string", MatchType.FUZZY)  
city = EFieldDefinition("city", "string", MatchType.FUZZY)  
areacode = EFieldDefinition("areacode", "string", MatchType.FUZZY)  
state = EFieldDefinition("state", "string", MatchType.FUZZY)  
dob = EFieldDefinition("dob", "string", MatchType.FUZZY)  
ssn = EFieldDefinition("ssn", "string", MatchType.FUZZY)  
  
fieldDefs = [recId, fname, lname, stNo, add1, add2, city, areacode, state, dob, ssn]  
args.setFieldDefinition(fieldDefs)  
#set the modelid and the zingg dir  
args.setModelId("100")  
args.setZinggDir("/tmp/models")  
args.setNumPartitions(4)  
args.setLabelDataSampleSize(0.5)  
  
#reading dataset into inputPipe and settint it up in 'args'  
schema = "recId string, fname string, lname string, stNo string, add1 string, add2 string, city string, areacode string, state string, dob string, ssn  string"  
inputPipe = ECsvPipe("testFebrl", "examples/febrl/test.csv", schema)  
args.setData(inputPipe)  
  
outputPipe = ECsvPipe("resultFebrl", "/tmp/febrlOutput")  
outputPipe.setHeader("true")  
  
args.setOutput(outputPipe)  
  
#Run findAndLabel  
options = ClientOptions([ClientOptions.PHASE,"findAndLabel"])  
zingg = EZingg(args, options)  
zingg.initAndExecute()  
  
#Run trainMatch after above completes  
options = ClientOptions([ClientOptions.PHASE,"trainMatch"])  
zingg = EZingg(args, options)  
zingg.initAndExecute()  
  
#Now run incremental on output generated above  
incrArgs = IncrementalArguments()  
incrArgs.setParentArgs(args)  
incrPipe = ECsvPipe("testFebrlIncr", "examples/febrl/test-incr.csv", schema)  
incrArgs.setIncrementalData(incrPipe)
outputTmpPipe = ECsvPipe("outputTmp", "/tmp/zinggOutput_febrl_tmp")
outputTmpPipe.setHeader("true") 
incrArgs.setOutputTmp(outputTmpPipe)
  
options = ClientOptions([ClientOptions.PHASE,"runIncremental"])  
zingg = EZingg(incrArgs, options)  
zingg.initAndExecute()  
```

[^1]: Zingg Enterprise is the suite of proprietary products licensed by Zingg. Please refer to <https://www.zingg.ai/product/zingg-entity-resolution-compare-versions> for individual tier features.


# Linking Across Datasets

In many cases like reference data mastering, enrichment, etc, two individual datasets are duplicate-free but they need to be matched against each other. The link phase is used for such scenarios.

`./zingg.sh --phase link --conf config.json`

Sample configuration file [configLink.json](https://github.com/zinggAI/zingg/tree/0.7.0/examples/febrl/configLink.json) is defined at [examples/febrl](https://github.com/zinggAI/zingg/tree/main/examples/febrl). In this option, each record from the first source is matched with all the records from the remaining sources.

The sample output is given in the image below. The linked records are given the same **z\_cluster** id. The last column (**z\_source**) in the output tells the source dataset of that record.

![Link Results](https://524808071-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGh6QkdUgeylq0gyuMmBw%2Fuploads%2Fgit-blob-b6b8516ed2a228ca4b3fd39574100b92ca3ae502%2Flink.png?alt=media)


# Lookup Data

[Zingg Enterprise Feature](#user-content-fn-1)[^1]

Sometimes we want to lookup certain records in match output, The lookup feature helps to achieve so. For given lookup records, it assigns zingg-id which decribes which entity cluster it belongs to.

### The lookup phase is run as follows:

`./scripts/zingg.sh --phase runLookup --conf <location to lookupConf.json>`

### Example lookupConf.json:

```json
{
  "config" : "config.json",
  "lookupData": [{
    "name":"lookup-test-data",
    "format":"inMemory"
  }
  ],
  "lookupOutput": [
    {
      "name":"lookup-output",
      "format":"csv",
      "props": {
        "path": "/tmp/zinggOutput/lookup",
        "delimiter": ",",
        "header":true
      }
    }
  ]
} 
```

[^1]: Zingg Enterprise is the suite of proprietary products licensed by Zingg. Please refer to <https://www.zingg.ai/product/zingg-entity-resolution-compare-versions> for individual tier features.


# Approval of Clusters

[Zingg Enterprise Feature](#user-content-fn-1)[^1]

### Coming Soon!

[^1]: Zingg Enterprise is the suite of proprietary products licensed by Zingg. Please refer to <https://www.zingg.ai/product/zingg-entity-resolution-compare-versions> for individual tier features.


# Reassign ZINGG ID

Reassign the ZINGG IDs for clusters from the original production model

[Zingg Enterprise Feature](#user-content-fn-1)[^1]

## Keep Your IDs Stable Across Changes

When you evolve your Zingg setup—whether upgrading your model, migrating infrastructure, changing data schemas, or moving to new platforms—you want to preserve the ZINGG IDs already flowing through your production systems. The **reassignZinggId** phase intelligently maximizes the preservation of original ZINGG IDs by mapping clusters from your new model back to your original cluster assignments, ensuring continuity and minimizing downstream disruption.

## Why Use Reassign?

* **Model Upgrades**: Add nickname support, improve blocking strategies, or tune classifiers without losing IDs
* **Infrastructure Migration**: Move from one platform to another (e.g., Spark to Snowflake) while maintaining ID continuity
* **Schema Changes**: Update your data schema or field definitions while preserving established IDs
* **Data Migration**: Migrate your data to new systems without disrupting operational workflows
* **Operational Continuity**: Downstream systems continue to work with stable, consistent IDs
* **Intelligent Mapping**: Automatically maximizes the reuse of original ZINGG IDs based on primary key overlap
* **Easy Integration**: No need to update downstream systems when you improve your model

## How It Works

The reassign phase compares two configurations:

1. **Original Configuration**: Your production model with established ZINGG IDs
2. **New Configuration**: Your updated model with improved features, new infrastructure, or schema changes

The reassign phase **maximizes the assignment of original ZINGG IDs** by:

* Identifying clusters in the new output that overlap with original clusters (via primary key matching)
* Reassigning the original ZINGG IDs to matching clusters wherever possible
* Generating new ZINGG IDs only when no match is found in the original clusters

## Usage

### Command Line

```bash
./scripts/zingg.sh --phase reassignZinggId \
  --conf examples/febrl/sparkIncremental/configReassign5M.json \
  --originalZinggId examples/febrl5M/config.json \
  --properties-file config/zingg.conf
```

**Key Parameters:**

* `--conf`: Points to a **wrapper configuration** that references your new/updated model config and specifies the output location for reassigned results
* `--originalZinggId`: Points to your **original production configuration**
* `--properties-file`: Zingg properties file (optional)

**Note:** The `--conf` parameter requires a wrapper configuration (see [Configuration Wrapper](#configuration-wrapper) section below) that includes a `transformedOutputPath` to specify where the reassigned output should be written. This is different from a plain model configuration file.

### Python API

Python users can pass configuration objects directly instead of JSON files.

```python
from zingg.client import *
from zingg.pipes import *
from zinggEC.enterprise.common.EArguments import *
from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition
from zinggES.enterprise.spark.ESparkClient import *
from zinggEC.enterprise.common.TransformedOutputArguments import *
from zinggEC.enterprise.common.EClientOptions import *

# Step 1: Define your ORIGINAL production configuration
# This is your current production setup with established ZINGG IDs
originalArgs = EArguments()
id = EFieldDefinition("id", "string", MatchType.DONT_USE)
id.setPrimaryKey(True)
fname = EFieldDefinition("fname", "string", MatchType.FUZZY)
lname = EFieldDefinition("lname", "string", MatchType.FUZZY)
stNo = EFieldDefinition("stNo", "string", MatchType.FUZZY)
add1 = EFieldDefinition("add1", "string", MatchType.FUZZY)
city = EFieldDefinition("city", "string", MatchType.FUZZY)
areacode = EFieldDefinition("areacode", "string", MatchType.FUZZY)
state = EFieldDefinition("state", "string", MatchType.FUZZY)
dob = EFieldDefinition("dob", "string", MatchType.FUZZY)
ssn = EFieldDefinition("ssn", "string", MatchType.FUZZY)

originalArgs.setFieldDefinition([id, fname, lname, stNo, add1, city, areacode, state, dob, ssn])
originalArgs.setModelId("107")
originalArgs.setZinggDir("./models")
originalArgs.setNumPartitions(4)

schema = "id string, fname string, lname string, stNo string, add1 string, city string, areacode string, state string, dob string, ssn string"
originalInputPipe = ECsvPipe("originalData", "examples/febrl5M/febrl_data_5M.csv.gz", schema)
originalArgs.setData(originalInputPipe)
originalOutputPipe = ECsvPipe("originalOutput", "/tmp/zinggOutputOriginal")
originalOutputPipe.setHeader("true")
originalArgs.setOutput(originalOutputPipe)

# IMPORTANT: trainMatch phase must already be completed for the original configuration
# The reassign phase reads the output from the original model

# Step 2: Define your NEW/UPDATED configuration
# Example: Changed some fields from FUZZY to EXACT matching (model upgrade scenario)
newArgs = EArguments()
id = EFieldDefinition("id", "string", MatchType.DONT_USE)
id.setPrimaryKey(True)
fname = EFieldDefinition("fname", "string", MatchType.FUZZY)
stNo = EFieldDefinition("stNo", "string", MatchType.EXACT)  # Changed from FUZZY
add1 = EFieldDefinition("add1", "string", MatchType.FUZZY)
city = EFieldDefinition("city", "string", MatchType.EXACT)   # Changed from FUZZY
areacode = EFieldDefinition("areacode", "string", MatchType.EXACT)  # Changed from FUZZY
state = EFieldDefinition("state", "string", MatchType.EXACT)  # Changed from FUZZY
dob = EFieldDefinition("dob", "string", MatchType.EXACT)  # Changed from FUZZY

newArgs.setFieldDefinition([id, fname, stNo, add1, city, areacode, state, dob])
newArgs.setModelId("999")
newArgs.setZinggDir("./models")
newArgs.setNumPartitions(4)
newArgs.setLabelDataSampleSize(0.5)

newInputPipe = ECsvPipe("newData", "examples/febrl5M/febrl_data_5M.csv.gz", schema)
newArgs.setData(newInputPipe)
newOutputPipe = ECsvPipe("newOutput", "/tmp/zinggOutputNew")
newOutputPipe.setHeader("true")
newArgs.setOutput(newOutputPipe)

# IMPORTANT: Run trainMatch on the new configuration first
# This generates the output that will be used for reassignment
options = EClientOptions([EClientOptions.PHASE, "trainMatch"])
zinggNew = EZingg(newArgs, options)
zinggNew.initAndExecute()

# Step 3: Create TransformedOutputArguments for reassignZinggId
reassignArgs = TransformedOutputArguments()
reassignArgs.setParentArgs(newArgs)        # New configuration
reassignArgs.setOriginalArgs(originalArgs) # Original configuration

# Set output path for reassigned results
reassignOutputPipe = ECsvPipe("reassignedOutput", "/tmp/zinggReassigned")
reassignOutputPipe.setHeader("true")
reassignArgs.setTransformedOutputPath(reassignOutputPipe)

# Step 4: Execute reassignZinggId
# The phase will read outputs from both configurations and maximize original ID preservation
reassignOptions = EClientOptions([EClientOptions.PHASE, "reassignZinggId"])
zinggReassign = EZingg(reassignArgs, reassignOptions)
zinggReassign.initAndExecute()
```

## Configuration Files

### Configuration Wrapper (`examples/febrl/sparkIncremental/configReassign5M.json`)

This wrapper configuration points to your new configuration and specifies where to write the reassigned output:

```json
{
    "config": "$ZINGG_ENT_REPO$/spark/examples/febrl5M/configUpdated.json",
    "transformedOutputPath": {
        "name": "reassignedOutput",
        "format": "csv",
        "props": {
            "location": "/tmp/zinggTransformedOutputReassigned5M",
            "delimiter": ",",
            "header": true
        }
    }
}
```

**Note**:

* This wrapper only points to the new configuration. The original configuration is specified via the `--originalZinggId` command-line parameter.
* The `name` field in `transformedOutputPath` can be any arbitrary identifier for the output pipe - it's used internally by Zingg to identify this output destination.

### New Configuration (`examples/febrl5M/configUpdated.json`)

Your updated model configuration with changes—for example, switching some fields from `fuzzy` to `exact` matching:

```json
{
  "fieldDefinition": [
    {
      "fieldName": "id",
      "matchType": "dont_use",
      "fields": "id",
      "dataType": "string",
      "primaryKey": true
    },
    {
      "fieldName": "fname",
      "matchType": "fuzzy",
      "fields": "fname",
      "dataType": "string"
    },
    {
      "fieldName": "stNo",
      "matchType": "exact",
      "fields": "stNo",
      "dataType": "string"
    },
    {
      "fieldName": "add1",
      "matchType": "fuzzy",
      "fields": "add1",
      "dataType": "string"
    },
    {
      "fieldName": "city",
      "matchType": "exact",
      "fields": "city",
      "dataType": "string"
    },
    {
      "fieldName": "areacode",
      "matchType": "exact",
      "fields": "areacode",
      "dataType": "string"
    },
    {
      "fieldName": "state",
      "matchType": "exact",
      "fields": "state",
      "dataType": "string"
    },
    {
      "fieldName": "dob",
      "matchType": "exact",
      "fields": "dob",
      "dataType": "string"
    }
  ],
  "output": [{
    "name": "OUTPUT_FEBRL",
    "format": "csv",
    "props": {
      "location": "/tmp/zinggOutputNew/",
      "delimiter": ",",
      "header": true
    }
  }],
  "data": [{
    "name": "test",
    "format": "csv",
    "props": {
      "location": "examples/febrl5M/febrl_data_5M.csv.gz",
      "delimiter": ",",
      "header": false
    },
    "schema": "id string, fname string, lname string, stNo string, add1 string, city string, areacode string, state string, dob string, ssn string"
  }],
  "outputStats": {
    "name": "stats",
    "format": "csv",
    "props": {
      "location": "/tmp/zinggStats_$ZINGG_DYNAMIC_STAT_NAME",
      "delimiter": ",",
      "header": true
    }
  },
  "labelDataSampleSize": 0.5,
  "numPartitions": 4,
  "modelId": 999,
  "zinggDir": "models"
}
```

### Original Configuration (`examples/febrl5M/config.json`)

Your production configuration with established ZINGG IDs:

```json
{
  "fieldDefinition": [
    {
      "fieldName": "id",
      "matchType": "dont_use",
      "fields": "id",
      "dataType": "string",
      "primaryKey": true
    },
    {
      "fieldName": "fname",
      "matchType": "fuzzy",
      "fields": "fname",
      "dataType": "string"
    },
    {
      "fieldName": "lname",
      "matchType": "fuzzy",
      "fields": "lname",
      "dataType": "string"
    },
    {
      "fieldName": "stNo",
      "matchType": "fuzzy",
      "fields": "stNo",
      "dataType": "string"
    },
    {
      "fieldName": "add1",
      "matchType": "fuzzy",
      "fields": "add1",
      "dataType": "string"
    },
    {
      "fieldName": "city",
      "matchType": "fuzzy",
      "fields": "city",
      "dataType": "string"
    },
    {
      "fieldName": "areacode",
      "matchType": "fuzzy",
      "fields": "areacode",
      "dataType": "string"
    },
    {
      "fieldName": "state",
      "matchType": "fuzzy",
      "fields": "state",
      "dataType": "string"
    },
    {
      "fieldName": "dob",
      "matchType": "fuzzy",
      "fields": "dob",
      "dataType": "string"
    },
    {
      "fieldName": "ssn",
      "matchType": "fuzzy",
      "fields": "ssn",
      "dataType": "string"
    }
  ],
  "output": [{
    "name": "output",
    "format": "csv",
    "props": {
      "location": "/tmp/zinggOutputOriginal",
      "delimiter": ",",
      "header": true
    }
  }],
  "data": [{
    "name": "test",
    "format": "csv",
    "props": {
      "location": "examples/febrl5M/febrl_data_5M.csv.gz",
      "delimiter": ",",
      "header": false
    },
    "schema": "id string, fname string, lname string, stNo string, add1 string, city string, areacode string, state string, dob string, ssn string"
  }],
  "outputStats": {
    "name": "stats",
    "format": "csv",
    "props": {
      "location": "/tmp/zinggStats_$ZINGG_DYNAMIC_STAT_NAME",
      "delimiter": ",",
      "header": true
    }
  },
  "labelDataSampleSize": 0.01,
  "numPartitions": 4000,
  "modelId": 107,
  "zinggDir": "models"
}
```

## Output Format

The output is written to the `transformedOutputPath` specified in your configuration and contains the same structure as the new model's output, but with reassigned ZINGG IDs that maximize preservation of the original IDs.

## Use Cases

### 1. Model Upgrades

When you add new features like nicknames, better blocking strategies, or improved classifiers, use reassignZinggId to maintain ID continuity.

### 2. Infrastructure Migration

Moving from Spark to Snowflake, or vice versa? Reassign helps you maintain the same ZINGG IDs across platforms.

### 3. Schema Evolution

If your data schema changes (new fields, removed fields, different field types), reassignZinggId preserves IDs for records that can still be matched via primary key.

### 4. Data Migration

Migrating from Databricks to Fabric, or vice versa? When migrating data to new systems or consolidating data sources, maintain ID consistency across the migration.

### 5. Platform Upgrades

Upgrading Spark versions, Snowflake features, or other platform components? Keep your IDs stable through the upgrade process.

[^1]: Zingg Enterprise is the suite of proprietary products licensed by Zingg. Please refer to <https://www.zingg.ai/product/zingg-entity-resolution-compare-versions> for individual tier features.


# Data Sources and Sinks

Zingg *connects, reads,* and *writes* to most on-premise and cloud data sources.

Zingg can read and write to **Databricks, Microsoft Fabric, Snowflake, Cassandra, S3, Azure, Elastic, Exasol**, major **RDBMS**, and any **Spark**-supported data sources.\
\
Zingg also works with all major file formats like Parquet, Avro, JSON, XLSX, CSV, TSV, etc. This is done through the Zingg [Pipe](/latest/connectors/pipes) abstraction.

![](https://524808071-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGh6QkdUgeylq0gyuMmBw%2Fuploads%2Fgit-blob-deab8fd6561bf5e4cef412aaba1000eeebab68b9%2FzinggOSS.png?alt=media)


# Zingg Pipes

Zingg Pipes are an *abstraction* for a data source from which Zingg fetches data for matching or to which Zingg writes its output. This lets users connect to literally any datastore that has a Spark connector.

The pipe is an easy way to specify *properties* and *formats* for the Spark connector of the relevant data source. Zingg pipes can be configured through the config [JSON](/latest/stepbystep/configuration) passed to the program by outlining the datastore connection properties.

Pipes can be configured for the data or the output attributes on the [JSON](/latest/stepbystep/configuration).

Each pipe has the following attributes:

## name

A unique name to identify the data store.

## format

One of the Spark-supported connector formats - jdbc/avro/parquet etc.

## options

Properties to be passed to **spark.read** and **spark.write.**

Let us look at some common data sources and their configurations.


# Databricks

As a Spark-based application, the **Zingg Community version** works seamlessly on Databricks. Zingg leverages Databricks' Spark environment.

You can access all the supported data sources like Parquet, Delta tables, and Unity Catalog (for governed Delta tables) while running **Zingg Community version**.

**Zingg Enterprise version** provides additional advanced features and support.

Please check the various ways in which you can run **Zingg Community version on Databricks** [here](/latest/running/databricks)


# Microsoft Fabric

As a Spark-based application, the **Zingg Community version** works seamlessly on Microsoft Fabric. Zingg leverages Fabric's Spark environment.

You can access all the supported data sources like parquet and other file formats while running **Zingg Community version**. **Zingg Enterprise version** supports additional advanced features.

Please check the various ways in which you can run **Zingg Community version on Microsoft Fabric** [here](/latest/running/running-on-fabric)


# Snowflake

Identity Resolution on Snowflake

Check a step-by-step tutorial at [Identity Resolution on Snowflake with open source Zingg](https://www.zingg.ai/documentation-article/identifying-duplicates-in-snowflake-with-zingg).

The config value for the data and output attributes of the JSON is:

```json
 "data" : [ {
			"name":"test", 
			"format":"net.snowflake.spark.snowflake", 
			"props": {
				"sfUrl": "rfa59271.snowflakecomputing.com",
				"sfUser": "sonalgoyal",
				"sfPassword":"ZZ",					
				"sfDatabase":"TEST",				
				"sfSchema":"PUBLIC",					
				"sfWarehouse":"COMPUTE_WH",
				"dbtable": "FEBRL",
				"application":"zingg_zingg"				
			}
		} ]
```

One must include Snowflake JDBC driver and Spark dependency on the classpath. The jars can be downloaded from the **maven** repository ([1](https://mvnrepository.com/artifact/net.snowflake/snowflake-jdbc), [2](https://mvnrepository.com/artifact/net.snowflake/spark-snowflake)).

```
spark.jars=snowflake-jdbc-3.13.19.jar,spark-snowflake_2.12-2.10.0-spark_3.1.jar
```

For Zingg to discover the Snowflake jars, please add the property **spark.jars** in [Zingg's runtime properties.](/latest/stepbystep/zingg-runtime-properties)

If you are looking for a native-run on Snowflake without using Spark, check [Zingg Enterprise Snowflake](https://www.zingg.ai/company/zingg-enterprise-snowflake).


# JDBC

Zingg can connect to various databases such as MySQL, DB2, MariaDB, MS SQL, Oracle, PostgreSQL, etc. using JDBC. One just needs to download the appropriate driver and made it accessible to the application.

To include the JDBC driver for your particular database on the Spark classpath, please add the property **spark.jars** in [Zingg's runtime properties.](/latest/stepbystep/zingg-runtime-properties)

```
spark.jars=<location of jdbc driver jar>
```

Connection details are given in the following sections for a few common JDBC sources.


# Postgres

## JSON Settings For Reading Data From Postgres Database:

```json
    "data" : [{
        "name":"test", 
        "format":"jdbc", 
        "props": {
            "url": "jdbc:postgresql://localhost:5432/<db_name>",
            "dbtable": "<testData>",
            "driver": "org.postgresql.Driver",
            "user": "<postgres>",
            "password": "<postgres>"				
        }
    }],
```

Replace `<db_name>` with the *name* of the database in addition to other props. For more details, refer to the [Spark documentation](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html).


# MySQL

## Reading From MySQL Database:

```json
    "data" : [{
        "name":"test", 
        "format":"jdbc", 
        "props": {
            "url": "jdbc:mysql://localhost:3306/<db_name>",
            "dbtable": "testData",
            "driver": "com.mysql.cj.jdbc.Driver",
            "user": "root",
            "password": "password"				
        }
    }],
```

Please replace `<db_name>` with the *name* of the database in addition to other props. For more details, refer to the [Spark documentation](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html).


# AWS S3

Zingg can use AWS S3 as a source and sink

## Steps to run zingg on S3

* Set a bucket, for example - *zingg28032023* and a folder inside it,for example - *zingg*
* Create aws access key and export via env vars (ensure that the user with below keys has read/write access to above)\
  `export AWS_ACCESS_KEY_ID=<access key id>`\
  `export AWS_SECRET_ACCESS_KEY=<access key>`\
  (if mfa is enabled AWS\_SESSION\_TOKEN env var would also be needed )
* Download *hadoop-aws-3.1.0.jar* and *aws-java-sdk-bundle-1.11.271.jar* via maven
* Set above in zingg.conf\
  spark.jars=//hadoop-aws-3.1.0.jar,//aws-java-sdk-bundle-1.11.271.jar
* Run using below commands

```bash
 ./scripts/zingg.sh --phase findTrainingData --properties-file config/zingg.conf  --conf examples/febrl/config.json --zinggDir  s3a://zingg28032023/zingg
 ./scripts/zingg.sh --phase label --properties-file config/zingg.conf  --conf examples/febrl/config.json --zinggDir  s3a://zingg28032023/zingg
 ./scripts/zingg.sh --phase train --properties-file config/zingg.conf  --conf examples/febrl/config.json --zinggDir  s3a://zingg28032023/zingg
 ./scripts/zingg.sh --phase match --properties-file config/zingg.conf  --conf examples/febrl/config.json --zinggDir  s3a://zingg28032023/zingg
```

## Model location

```
Models etc. would get saved in 
Amazon S3 > Buckets > zingg28032023 > zingg > 100
```


# Cassandra

```json
"output" : [
			{
			"name":"sampleTest", 
			"format":"CASSANDRA" ,
			"props": {
				"table":"dataschematest",
				"keyspace":"zingg",
				"cluster":"zingg",
				"spark.cassandra.connection.host":"192.168.0.6"
			},
			"sparkProps": {
				"spark.cassandra.connection.host":"127.0.0.1"
			},
			"mode":"Append"
		}
		]
```


# MongoDB

```json
"data" : [{
		"name":"mongodb", 
		"format":"mongo", 
		"props": {
			"uri": "mongodb://127.0.0.1/people.contacts"		
			}	
		}]

```


# Neo4j

```json
"data" : [{
		"name":"neo", 
		"format":"org.neo4j.spark.DataSource", 
		"props": {
			"url": "bolt://localhost:7687",
            "labels":"Person"		
			}	
		}]
```


# Parquet

```json
"data" : [{
		"name":"parquetFiles", 
		"format":"parquet", 
		"props": {
			"path": "/home/zingg"		
			}	
		}]
```


# BigQuery

Zingg can seamlessly work with Google BigQuery. Please find below details about the properties that must be set.

The two driver jars namely **spark-bigquery-with-dependencies\_2.12-0.24.2.jar** and **gcs-connector-hadoop2-latest.jar** are required to work with BigQuery. To include these BigQuery drivers in the classpath, please configure the [runtime properties](/latest/stepbystep/zingg-runtime-properties) to include these

```bash
spark.jars=./spark-bigquery-with-dependencies_2.12-0.24.2.jar,./gcs-connector-hadoop2-latest.jar
```

In addition, the following property needs to be set

```bash
spark.hadoop.fs.gs.impl=com.google.cloud.hadoop.fs.gcs.GoogleHadoopFileSystem                                                      
```

If Zingg is run from outside the Google cloud, it requires authentication, please set the following *environment variable* to the location of the file containing the *service account key*. A service account key can be created and downloaded in JSON format from the [Google Cloud console](https://cloud.google.com/docs/authentication/getting-started).

```bash
export GOOGLE_APPLICATION_CREDENTIALS=path to google service account key file
```

Connection properties for BigQuery as a data source and data sink are given below. If you are curious to know more about how Spark connects to BigQuery, you may look at the [Spark BigQuery connector documentation](https://github.com/GoogleCloudDataproc/spark-bigquery-connector).

## Properties for reading data from BigQuery:

The property `credentialsFile` should point to the Google service account key file location. This is the same path that is used to set variable `GOOGLE_APPLICATION_CREDENTIALS`. The `table` property should point to a BigQuery table that contains source data. The property `viewsEnabled` must be set to **true** only.

```json
    "data" : [{
        "name":"test", 
         "format":"bigquery", 
        "props": {
            "credentialsFile": "/home/work/product/final/zingg-1/mynotification-46566-905cbfd2723f.json",
            "table": "mynotification-46566.zinggdataset.zinggtest",
            "viewsEnabled": true
        }
    }],
```

## Properties For Writing Data To BigQuery:

To write to BigQuery, a bucket needs to be created and assigned to the `temporaryGcsBucket` property.

```json
    "output" : [{
        "name":"output", 
        "format":"bigquery",
        "props": {
            "credentialsFile": "/home/work/product/final/zingg-1/mynotification-46566-905cbfd2723f.json",
            "table": "mynotification-46566.zinggdataset.zinggOutput",
            "temporaryGcsBucket":"zingg-test",
         }
    }],
```

## Notes:

* The library **"gcs-connector-hadoop2-latest.jar"** can be downloaded from [Google](https://storage.googleapis.com/hadoop-lib/gcs/gcs-connector-hadoop2-latest.jar) and the library **"spark-bigquery-with-dependencies\_2.12-0.24.2"** from the [maven repo](https://repo1.maven.org/maven2/com/google/cloud/spark/spark-bigquery-with-dependencies_2.12/0.24.2/spark-bigquery-with-dependencies_2.12-0.24.2.jar).
* A typical service account key file looks like below (JSON).

```json
{
 "type": "service_account",
 "project_id": "mynotification-46566",
 "private_key_id": "905cbfd273ff9205d1ce06fa6908e54534",
 "private_key": "-----BEGIN PRIVATE KEY-----CERT.....",
 "client_id": "11143646541215487",
 "auth_uri": "https://accounts.google.com/o/oauth2/auth",
 "token_uri": "https://oauth2.googleapis.com/token",
 "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
 "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/zingtest%44mynotification-46566.iam.gserviceaccount.com"
}
```


# Exasol

[Exasol](https://www.exasol.com/) is a in-memory database built for analytics. You can use it to interact with Zingg AI.

## Prerequisites

To use the Exasol database with Zingg, first you should download the required dependencies and set them in the Zingg configuration file.

### Download Assembled Exasol Spark Connector Jar

Please download the latest assembled Exasol [spark-connector](https://github.com/exasol/spark-connector/releases) jar file. Go to the `Assets` section below, and make sure you download the jar file with `-assembly` suffix in the file name.

### Update Zingg Configuration

After downloading the Exasol spark-connector jar file, you should update the `spark.jars` parameter in [Zingg's runtime properties.](/latest/stepbystep/zingg-runtime-properties) so that it can find the Exasol dependencies.

For example:

```
spark.jars=spark-connector_2.12-1.3.0-spark-3.3.2-assembly.jar
```

If there are more than one jar files, please use *comma* as separator. Additionally, please change the version accordingly so that it matches your Zingg and Spark versions.

## Connector Settings

Finally, create a configuration JSON file for Zingg, and update the `data` or `output` settings accordingly.

For example:

```json
...
 "data": [
    {
        "name": "input",
        "format": "com.exasol.spark",
        "props": {
            "host": "10.11.0.2",
            "port": "8563",
            "username": "sys",
            "password": "exasol",
            "query": "SELECT * FROM DB_SCHEMA.CUSTOMERS"
        }
    }
 ],
 ...
```

Similarly, for output:

```json
...
"output": [
   {
       "name": "output",
       "format": "com.exasol.spark",
       "props": {
           "host": "10.11.0.2",
           "port": "8563",
           "username": "sys",
           "password": "exasol",
           "create_table": "true",
           "table": "DB_SCHEMA.ENTITY_RESOLUTION",
       },
       "mode": "Append"
   }
],
...
```

Please note that, the `host` parameter should be the first internal node's **IPv4** **address**.

As Zingg uses [Exasol Spark connector](https://github.com/exasol/spark-connector) underneath, please also check out the [user guide](https://github.com/exasol/spark-connector/blob/main/doc/user_guide/user_guide.md) and [configuration options](https://github.com/exasol/spark-connector/blob/main/doc/user_guide/user_guide.md#configuration-options) for more information.


# Working With Python

A whole new way to work with Zingg!

Instead of configuring Zingg using JSON, we can now use Python to build and run Zingg entity and identity resolution programs. This is handy when you want to run Zingg on an existing Spark cluster. To run on a local machine, please install from the release before running Zingg Python programs.

The Zingg Python package can be installed by invoking:

`python -m pip install zingg`

Detailed documentation of the Python API is available at:<https://readthedocs.org/projects/zingg/>

Example programs for Python exist under [examples](https://github.com/zinggAI/zingg/tree/main/examples/febrl). Please check [examples/febrl/FebrlExample.py](https://github.com/zinggAI/zingg/blob/main/examples/febrl/FebrlExample.py) to get started.

Please refer to the [command line guide](/latest/stepbystep/zingg-command-line) for running Python programs. Please note that Zingg Python programs are PySpark programs and hence need the Zingg CLI to execute.


# Community Python API

## Community Zingg Entity Resolution Python Package

Community Zingg Python APIs for entity resolution, identity resolution, record linkage, data mastering and deduplication using ML (<https://www.zingg.ai>)

**NOTE**

Requires python 3.6+; spark 3.5.0 Otherwise, [`zingg.client.Zingg()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonOss/markdown/_autosummary/zingg.client.Zingg.md#zingg.client.Zingg) cannot be executed

## API Documentation

* [zingg.client module](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonOss/markdown/zingg.client.md)
  * [Classes](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonOss/markdown/zingg.client.md#classes)
    * [zingg.client.Zingg](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonOss/markdown/_autosummary/zingg.client.Zingg.md)
    * [zingg.client.ZinggWithSpark](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonOss/markdown/_autosummary/zingg.client.ZinggWithSpark.md)
    * [zingg.client.Arguments](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonOss/markdown/_autosummary/zingg.client.Arguments.md)
    * [zingg.client.ClientOptions](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonOss/markdown/_autosummary/zingg.client.ClientOptions.md)
    * [zingg.client.FieldDefinition](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonOss/markdown/_autosummary/zingg.client.FieldDefinition.md)
* [zingg.pipes module](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonOss/markdown/zingg.pipes.md)
  * [Classes](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonOss/markdown/zingg.pipes.md#classes)
    * [zingg.pipes.Pipe](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonOss/markdown/_autosummary/zingg.pipes.Pipe.md)
    * [zingg.pipes.CsvPipe](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonOss/markdown/_autosummary/zingg.pipes.CsvPipe.md)
    * [zingg.pipes.BigQueryPipe](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonOss/markdown/_autosummary/zingg.pipes.BigQueryPipe.md)
    * [zingg.pipes.SnowflakePipe](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonOss/markdown/_autosummary/zingg.pipes.SnowflakePipe.md)

## Example API Usage

```python
from zingg.client import *
from zingg.pipes import *

#build the arguments for zingg
args = Arguments()
#set field definitions
fname = FieldDefinition("fname", "string", MatchType.FUZZY)
lname = FieldDefinition("lname", "string", MatchType.FUZZY)
stNo = FieldDefinition("stNo", "string", MatchType.FUZZY)
add1 = FieldDefinition("add1","string", MatchType.FUZZY)
add2 = FieldDefinition("add2", "string", MatchType.FUZZY)
city = FieldDefinition("city", "string", MatchType.FUZZY)
areacode = FieldDefinition("areacode", "string", MatchType.FUZZY)
state = FieldDefinition("state", "string", MatchType.FUZZY)
dob = FieldDefinition("dob", "string", MatchType.FUZZY)
ssn = FieldDefinition("ssn", "string", MatchType.FUZZY)

fieldDefs = [fname, lname, stNo, add1, add2, city, areacode, state, dob, ssn]

args.setFieldDefinition(fieldDefs)
#set the modelid and the zingg dir
args.setModelId("100")
args.setZinggDir("models")
args.setNumPartitions(4)
args.setLabelDataSampleSize(0.5)

#reading dataset into inputPipe and setting it up in 'args'
schema = "id string, fname string, lname string, stNo string, add1 string, add2 string, city string, areacode string, state string, dob string, ssn  string"
inputPipe = CsvPipe("testFebrl", "examples/febrl/test.csv", schema)
args.setData(inputPipe)
outputPipe = CsvPipe("resultFebrl", "/tmp/febrlOutput")

args.setOutput(outputPipe)

options = ClientOptions([ClientOptions.PHASE,"match"])

#Zingg execution for the given phase
zingg = Zingg(args, options)
zingg.initAndExecute()
```


# Enterprise Python API

A whole new way to work with Zingg Enterprise!

## Example API Usage

```python
from zingg.client import *
from zingg.pipes import *
from zinggEC.enterprise.common.ApproverArguments import *
from zinggEC.enterprise.common.IncrementalArguments import *
from zinggEC.enterprise.common.MappingMatchType import *
from zinggEC.enterprise.common.epipes import *
from zinggEC.enterprise.common.EArguments import *
from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition
from zinggES.enterprise.spark.ESparkClient import *
import os

#build the arguments for zingg
args = EArguments()
#set field definitions
recId = EFieldDefinition("recId", "string", MatchType.DONT_USE)
recId.setPrimaryKey(True)
fname = EFieldDefinition("fname", "string", MatchType.FUZZY)
# for mapping match type
#fname = EFieldDefinition("fname", "string", MatchType.FUZZY, MappingMatchType("MAPPING", "NICKNAMES_TEST"))
lname = EFieldDefinition("lname", "string", MatchType.FUZZY)
stNo = EFieldDefinition("stNo", "string", MatchType.FUZZY)
add1 = EFieldDefinition("add1","string", MatchType.FUZZY)
add2 = EFieldDefinition("add2", "string", MatchType.FUZZY)
city = EFieldDefinition("city", "string", MatchType.FUZZY)
areacode = EFieldDefinition("areacode", "string", MatchType.FUZZY)
state = EFieldDefinition("state", "string", MatchType.FUZZY)
dob = EFieldDefinition("dob", "string", MatchType.FUZZY)
ssn = EFieldDefinition("ssn", "string", MatchType.FUZZY)

fieldDefs = [recId, fname, lname, stNo, add1, add2, city, areacode, state, dob, ssn]
args.setFieldDefinition(fieldDefs)
#set the modelid and the zingg dir
args.setModelId("100")
args.setZinggDir("./models")
args.setNumPartitions(4)
args.setLabelDataSampleSize(0.5)

# Set the blocking strategy for the Zingg Model as either DEFAULT or WIDER - if you do not set anything, the model follows DEFAULT strategy
args.setBlockingModel("DEFAULT")

#setting pass thru condition
args.setPassthroughExpr("fname = 'matilda'")

#setting deterministic matching conditions
dm1 = DeterministicMatching('fname','stNo','add1')
dm2 = DeterministicMatching('ssn')
dm3 = DeterministicMatching('fname','stNo','lname')
args.setDeterministicMatchingCondition(dm1,dm2,dm3)

#reading dataset into inputPipe and setting it up in 'args'
#below line should not be required if you are reading from in memory dataset
#in that case, replace df with input df
schema = "recId string, fname string, lname string, stNo string, add1 string, add2 string, city string, areacode string, state string, dob string, ssn  string"
inputPipe = ECsvPipe("testFebrl", "examples/febrl/test.csv", schema)
args.setData(inputPipe)

outputPipe = ECsvPipe("resultFebrl", "/tmp/febrlOutput")
outputPipe.setHeader("true")
args.setOutput(outputPipe)

# Zingg execution for the given phase
# options = ClientOptions([ClientOptions.PHASE,"findAndLabel"])

options = ClientOptions([ClientOptions.PHASE,"trainMatch"])
zingg = EZingg(args, options)
zingg.initAndExecute()

incrArgs = IncrementalArguments()
incrArgs.setParentArgs(args)
incrPipe = ECsvPipe("testFebrlIncr", "examples/febrl/test-incr.csv", schema)
incrArgs.setIncrementalData(incrPipe)

incrOptions = ClientOptions([ClientOptions.PHASE,"runIncremental"])
zinggIncr = EZingg(incrArgs, incrOptions)
zinggIncr.initAndExecute()
```


# ZinggEC Python API

## Zingg Enterpise Entity Resolution Python Package

Zingg Enterprise Python APIs for entity resolution, identity resolution, record linkage, data mastering and deduplication using ML (<https://www.zingg.ai>)

**NOTE**

Requires python 3.6+; spark 3.5.0 Otherwise, `zinggES.enterprise.spark.ESparkClient()` cannot be executed

* [Zingg Enterprise Common Package](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/zinggEC.md)
  * [ApproverArguments](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/ApproverArguments.md)
    * [zinggEC.enterprise.common.ApproverArguments](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/ApproverArguments.md#zinggec-enterprise-common-approverarguments)
    * [`ApproverArguments`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/ApproverArguments.md#zinggEC.enterprise.common.ApproverArguments.ApproverArguments)
      * [`ApproverArguments.getApprovalQuery()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/ApproverArguments.md#zinggEC.enterprise.common.ApproverArguments.ApproverArguments.getApprovalQuery)
      * [`ApproverArguments.getArgs()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/ApproverArguments.md#zinggEC.enterprise.common.ApproverArguments.ApproverArguments.getArgs)
      * [`ApproverArguments.getDestination()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/ApproverArguments.md#zinggEC.enterprise.common.ApproverArguments.ApproverArguments.getDestination)
      * [`ApproverArguments.getParentArgs()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/ApproverArguments.md#zinggEC.enterprise.common.ApproverArguments.ApproverArguments.getParentArgs)
      * [`ApproverArguments.setApprovalQuery()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/ApproverArguments.md#zinggEC.enterprise.common.ApproverArguments.ApproverArguments.setApprovalQuery)
      * [`ApproverArguments.setArgs()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/ApproverArguments.md#zinggEC.enterprise.common.ApproverArguments.ApproverArguments.setArgs)
      * [`ApproverArguments.setDestination()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/ApproverArguments.md#zinggEC.enterprise.common.ApproverArguments.ApproverArguments.setDestination)
      * [`ApproverArguments.setParentArgs()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/ApproverArguments.md#zinggEC.enterprise.common.ApproverArguments.ApproverArguments.setParentArgs)
  * [IncrementalArguments](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/IncrementalArguments.md)
    * [zinggEC.enterprise.common.IncrementalArguments](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/IncrementalArguments.md#zinggec-enterprise-common-incrementalarguments)
    * [`IncrementalArguments`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/IncrementalArguments.md#zinggEC.enterprise.common.IncrementalArguments.IncrementalArguments)
      * [`IncrementalArguments.getArgs()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/IncrementalArguments.md#zinggEC.enterprise.common.IncrementalArguments.IncrementalArguments.getArgs)
      * [`IncrementalArguments.getDeleteAction()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/IncrementalArguments.md#zinggEC.enterprise.common.IncrementalArguments.IncrementalArguments.getDeleteAction)
      * [`IncrementalArguments.getDeletedData()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/IncrementalArguments.md#zinggEC.enterprise.common.IncrementalArguments.IncrementalArguments.getDeletedData)
      * [`IncrementalArguments.getIncrementalData()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/IncrementalArguments.md#zinggEC.enterprise.common.IncrementalArguments.IncrementalArguments.getIncrementalData)
      * [`IncrementalArguments.getOutputTmp()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/IncrementalArguments.md#zinggEC.enterprise.common.IncrementalArguments.IncrementalArguments.getOutputTmp)
      * [`IncrementalArguments.getParentArgs()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/IncrementalArguments.md#zinggEC.enterprise.common.IncrementalArguments.IncrementalArguments.getParentArgs)
      * [`IncrementalArguments.setArgs()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/IncrementalArguments.md#zinggEC.enterprise.common.IncrementalArguments.IncrementalArguments.setArgs)
      * [`IncrementalArguments.setDeleteAction()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/IncrementalArguments.md#zinggEC.enterprise.common.IncrementalArguments.IncrementalArguments.setDeleteAction)
      * [`IncrementalArguments.setDeletedData()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/IncrementalArguments.md#zinggEC.enterprise.common.IncrementalArguments.IncrementalArguments.setDeletedData)
      * [`IncrementalArguments.setIncrementalData()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/IncrementalArguments.md#zinggEC.enterprise.common.IncrementalArguments.IncrementalArguments.setIncrementalData)
      * [`IncrementalArguments.setOutputTmp()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/IncrementalArguments.md#zinggEC.enterprise.common.IncrementalArguments.IncrementalArguments.setOutputTmp)
      * [`IncrementalArguments.setParentArgs()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/IncrementalArguments.md#zinggEC.enterprise.common.IncrementalArguments.IncrementalArguments.setParentArgs)
  * [MappingMatchType](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/MappingMatchType.md)
    * [zinggEC.enterprise.common.MappingMatchType](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/MappingMatchType.md#zinggec-enterprise-common-mappingmatchtype)
    * [`MappingMatchType`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/MappingMatchType.md#zinggEC.enterprise.common.MappingMatchType.MappingMatchType)
      * [`MappingMatchType.getMappingMatchType()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/MappingMatchType.md#zinggEC.enterprise.common.MappingMatchType.MappingMatchType.getMappingMatchType)
  * [epipes](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/epipes.md)
    * [zinggEC.enterprise.common.epipes](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/epipes.md#zinggec-enterprise-common-epipes)
    * [`ECsvPipe`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/epipes.md#zinggEC.enterprise.common.epipes.ECsvPipe)
      * [`ECsvPipe.setDelimiter()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/epipes.md#zinggEC.enterprise.common.epipes.ECsvPipe.setDelimiter)
      * [`ECsvPipe.setHeader()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/epipes.md#zinggEC.enterprise.common.epipes.ECsvPipe.setHeader)
      * [`ECsvPipe.setLocation()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/epipes.md#zinggEC.enterprise.common.epipes.ECsvPipe.setLocation)
    * [`EPipe`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/epipes.md#zinggEC.enterprise.common.epipes.EPipe)
      * [`EPipe.getPassthroughExpr()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/epipes.md#zinggEC.enterprise.common.epipes.EPipe.getPassthroughExpr)
      * [`EPipe.getPassthruData()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/epipes.md#zinggEC.enterprise.common.epipes.EPipe.getPassthruData)
      * [`EPipe.getUsableData()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/epipes.md#zinggEC.enterprise.common.epipes.EPipe.getUsableData)
      * [`EPipe.hasPassThru()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/epipes.md#zinggEC.enterprise.common.epipes.EPipe.hasPassThru)
      * [`EPipe.setPassthroughExpr()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/epipes.md#zinggEC.enterprise.common.epipes.EPipe.setPassthroughExpr)
    * [`InMemoryPipe`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/epipes.md#zinggEC.enterprise.common.epipes.InMemoryPipe)
      * [`InMemoryPipe.getDataset()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/epipes.md#zinggEC.enterprise.common.epipes.InMemoryPipe.getDataset)
      * [`InMemoryPipe.setDataset()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/epipes.md#zinggEC.enterprise.common.epipes.InMemoryPipe.setDataset)
    * [`UCPipe`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/epipes.md#zinggEC.enterprise.common.epipes.UCPipe)
      * [`UCPipe.setTable()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/epipes.md#zinggEC.enterprise.common.epipes.UCPipe.setTable)
  * [EArguments](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/EArguments.md)
    * [zinggEC.enterprise.common.EArguments](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/EArguments.md#zinggec-enterprise-common-earguments)
    * [`DeterministicMatching`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/EArguments.md#zinggEC.enterprise.common.EArguments.DeterministicMatching)
      * [`DeterministicMatching.getDeterministicMatching()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/EArguments.md#zinggEC.enterprise.common.EArguments.DeterministicMatching.getDeterministicMatching)
    * [`EArguments`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/EArguments.md#zinggEC.enterprise.common.EArguments.EArguments)
      * [`EArguments.getArgs()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/EArguments.md#zinggEC.enterprise.common.EArguments.EArguments.getArgs)
      * [`EArguments.getData()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/EArguments.md#zinggEC.enterprise.common.EArguments.EArguments.getData)
      * [`EArguments.getDeterministicMatching()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/EArguments.md#zinggEC.enterprise.common.EArguments.EArguments.getDeterministicMatching)
      * [`EArguments.getFieldDefinition()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/EArguments.md#zinggEC.enterprise.common.EArguments.EArguments.getFieldDefinition)
      * [`EArguments.getOutputStats()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/EArguments.md#zinggEC.enterprise.common.EArguments.EArguments.getOutputStats)
      * [`EArguments.getPassthroughExpr()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/EArguments.md#zinggEC.enterprise.common.EArguments.EArguments.getPassthroughExpr)
      * [`EArguments.getPrimaryKey()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/EArguments.md#zinggEC.enterprise.common.EArguments.EArguments.getPrimaryKey)
      * [`EArguments.setArgs()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/EArguments.md#zinggEC.enterprise.common.EArguments.EArguments.setArgs)
      * [`EArguments.setBlockingModel()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/EArguments.md#zinggEC.enterprise.common.EArguments.EArguments.setBlockingModel)
      * [`EArguments.setData()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/EArguments.md#zinggEC.enterprise.common.EArguments.EArguments.setData)
      * [`EArguments.setDeterministicMatchingCondition()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/EArguments.md#zinggEC.enterprise.common.EArguments.EArguments.setDeterministicMatchingCondition)
      * [`EArguments.setFieldDefinition()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/EArguments.md#zinggEC.enterprise.common.EArguments.EArguments.setFieldDefinition)
      * [`EArguments.setOutputStats()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/EArguments.md#zinggEC.enterprise.common.EArguments.EArguments.setOutputStats)
      * [`EArguments.setPassthroughExpr()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/EArguments.md#zinggEC.enterprise.common.EArguments.EArguments.setPassthroughExpr)
  * [EFieldDefinition](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/EFieldDefinition.md)
    * [zinggEC.enterprise.common.EFieldDefinition](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/EFieldDefinition.md#zinggec-enterprise-common-efielddefinition)
    * [`EFieldDefinition`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/EFieldDefinition.md#zinggEC.enterprise.common.EFieldDefinition.EFieldDefinition)
      * [`EFieldDefinition.getMatchTypeArray()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/EFieldDefinition.md#zinggEC.enterprise.common.EFieldDefinition.EFieldDefinition.getMatchTypeArray)
      * [`EFieldDefinition.getPrimaryKey()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/EFieldDefinition.md#zinggEC.enterprise.common.EFieldDefinition.EFieldDefinition.getPrimaryKey)
      * [`EFieldDefinition.setPrimaryKey()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/EFieldDefinition.md#zinggEC.enterprise.common.EFieldDefinition.EFieldDefinition.setPrimaryKey)

## API Reference

* [Module Index](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/py-modindex.md)
* [Index](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/genindex.md)
* [Search Page](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonEC/markdown/search.md)

## Example API Usage

```python
from zingg.client import *
from zingg.pipes import *
from zinggEC.enterprise.common.ApproverArguments import *
from zinggEC.enterprise.common.IncrementalArguments import *
from zinggEC.enterprise.common.MappingMatchType import *
from zinggEC.enterprise.common.epipes import *
from zinggEC.enterprise.common.EArguments import *
from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition
from zinggES.enterprise.spark.ESparkClient import *
import os

#build the arguments for zingg
args = EArguments()
#set field definitions
recId = EFieldDefinition("recId", "string", MatchType.DONT_USE)
recId.setPrimaryKey(True)
fname = EFieldDefinition("fname", "string", MatchType.FUZZY)
# for mapping match type
#fname = EFieldDefinition("fname", "string", MatchType.FUZZY, MappingMatchType("MAPPING", "NICKNAMES_TEST"))
lname = EFieldDefinition("lname", "string", MatchType.FUZZY)
stNo = EFieldDefinition("stNo", "string", MatchType.FUZZY)
add1 = EFieldDefinition("add1","string", MatchType.FUZZY)
add2 = EFieldDefinition("add2", "string", MatchType.FUZZY)
city = EFieldDefinition("city", "string", MatchType.FUZZY)
areacode = EFieldDefinition("areacode", "string", MatchType.FUZZY)
state = EFieldDefinition("state", "string", MatchType.FUZZY)
dob = EFieldDefinition("dob", "string", MatchType.FUZZY)
ssn = EFieldDefinition("ssn", "string", MatchType.FUZZY)

fieldDefs = [recId, fname, lname, stNo, add1, add2, city, areacode, state, dob, ssn]
args.setFieldDefinition(fieldDefs)
#set the modelid and the zingg dir
args.setModelId("100")
args.setZinggDir("./models")
args.setNumPartitions(4)
args.setLabelDataSampleSize(0.5)

# Set the blocking strategy for the Zingg Model as either DEFAULT or WIDER - if you do not set anything, the model follows DEFAULT strategy
args.setBlockingModel("DEFAULT")

#setting pass thru condition
args.setPassthroughExpr("fname = 'matilda'")

#setting deterministic matching conditions
dm1 = DeterministicMatching('fname','stNo','add1')
dm2 = DeterministicMatching('ssn')
dm3 = DeterministicMatching('fname','stNo','lname')
args.setDeterministicMatchingCondition(dm1,dm2,dm3)

#reading dataset into inputPipe and setting it up in 'args'
#below line should not be required if you are reading from in memory dataset
#in that case, replace df with input df
schema = "recId string, fname string, lname string, stNo string, add1 string, add2 string, city string, areacode string, state string, dob string, ssn  string"
inputPipe = ECsvPipe("testFebrl", "examples/febrl/test.csv", schema)
args.setData(inputPipe)

outputPipe = ECsvPipe("resultFebrl", "/tmp/febrlOutput")
outputPipe.setHeader("true")
args.setOutput(outputPipe)

# Zingg execution for the given phase
# options = ClientOptions([ClientOptions.PHASE,"findAndLabel"])

options = ClientOptions([ClientOptions.PHASE,"trainMatch"])
zingg = EZingg(args, options)
zingg.initAndExecute()

incrArgs = IncrementalArguments()
incrArgs.setParentArgs(args)
incrPipe = ECsvPipe("testFebrlIncr", "examples/febrl/test-incr.csv", schema)
incrArgs.setIncrementalData(incrPipe)

incrOptions = ClientOptions([ClientOptions.PHASE,"runIncremental"])
zinggIncr = EZingg(incrArgs, incrOptions)
zinggIncr.initAndExecute()
```


# ZinggES Python API

## Zingg Enterpise Entity Resolution Python Package

Zingg Enterprise Python APIs for entity resolution, identity resolution, record linkage, data mastering and deduplication using ML (<https://www.zingg.ai>)

**NOTE**

Requires python 3.6+; spark 3.5.0 Otherwise, [`zinggES.enterprise.spark.ESparkClient()`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonES/markdown/ESparkClient.md#module-zinggES.enterprise.spark.ESparkClient) cannot be executed

* [Zingg Enterprise Spark Package](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonES/markdown/zinggES.md)
  * [ESparkClient](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonES/markdown/ESparkClient.md)
    * [zinggES.enterprise.spark.ESparkClient](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonES/markdown/ESparkClient.md#zingges-enterprise-spark-esparkclient)
    * [`EZingg`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonES/markdown/ESparkClient.md#zinggES.enterprise.spark.ESparkClient.EZingg)
    * [`EZinggWithSpark`](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonES/markdown/ESparkClient.md#zinggES.enterprise.spark.ESparkClient.EZinggWithSpark)

## API Reference

* [Module Index](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonES/markdown/py-modindex.md)
* [Index](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonES/markdown/genindex.md)
* [Search Page](https://github.com/zinggAI/zingg/tree/0.7.0/docs/pythonES/markdown/search.md)

## Example API Usage

```python
from zingg.client import *
from zingg.pipes import *
from zinggEC.enterprise.common.ApproverArguments import *
from zinggEC.enterprise.common.IncrementalArguments import *
from zinggEC.enterprise.common.MappingMatchType import *
from zinggEC.enterprise.common.epipes import *
from zinggEC.enterprise.common.EArguments import *
from zinggEC.enterprise.common.EFieldDefinition import EFieldDefinition
from zinggES.enterprise.spark.ESparkClient import *
import os

#build the arguments for zingg
args = EArguments()
#set field definitions
recId = EFieldDefinition("recId", "string", MatchType.DONT_USE)
recId.setPrimaryKey(True)
fname = EFieldDefinition("fname", "string", MatchType.FUZZY)
# for mapping match type
#fname = EFieldDefinition("fname", "string", MatchType.FUZZY, MappingMatchType("MAPPING", "NICKNAMES_TEST"))
lname = EFieldDefinition("lname", "string", MatchType.FUZZY)
stNo = EFieldDefinition("stNo", "string", MatchType.FUZZY)
add1 = EFieldDefinition("add1","string", MatchType.FUZZY)
add2 = EFieldDefinition("add2", "string", MatchType.FUZZY)
city = EFieldDefinition("city", "string", MatchType.FUZZY)
areacode = EFieldDefinition("areacode", "string", MatchType.FUZZY)
state = EFieldDefinition("state", "string", MatchType.FUZZY)
dob = EFieldDefinition("dob", "string", MatchType.FUZZY)
ssn = EFieldDefinition("ssn", "string", MatchType.FUZZY)

fieldDefs = [recId, fname, lname, stNo, add1, add2, city, areacode, state, dob, ssn]
args.setFieldDefinition(fieldDefs)
#set the modelid and the zingg dir
args.setModelId("100")
args.setZinggDir("./models")
args.setNumPartitions(4)
args.setLabelDataSampleSize(0.5)

# Set the blocking strategy for the Zingg Model as either DEFAULT or WIDER - if you do not set anything, the model follows DEFAULT strategy
args.setBlockingModel("DEFAULT")

#setting pass thru condition
args.setPassthroughExpr("fname = 'matilda'")

#setting deterministic matching conditions
dm1 = DeterministicMatching('fname','stNo','add1')
dm2 = DeterministicMatching('ssn')
dm3 = DeterministicMatching('fname','stNo','lname')
args.setDeterministicMatchingCondition(dm1,dm2,dm3)

#reading dataset into inputPipe and setting it up in 'args'
#below line should not be required if you are reading from in memory dataset
#in that case, replace df with input df
schema = "recId string, fname string, lname string, stNo string, add1 string, add2 string, city string, areacode string, state string, dob string, ssn  string"
inputPipe = ECsvPipe("testFebrl", "examples/febrl/test.csv", schema)
args.setData(inputPipe)

outputPipe = ECsvPipe("resultFebrl", "/tmp/febrlOutput")
outputPipe.setHeader("true")
args.setOutput(outputPipe)

# Zingg execution for the given phase
# options = ClientOptions([ClientOptions.PHASE,"findAndLabel"])

options = ClientOptions([ClientOptions.PHASE,"trainMatch"])
zingg = EZingg(args, options)
zingg.initAndExecute()

incrArgs = IncrementalArguments()
incrArgs.setParentArgs(args)
incrPipe = ECsvPipe("testFebrlIncr", "examples/febrl/test-incr.csv", schema)
incrArgs.setIncrementalData(incrPipe)

incrOptions = ClientOptions([ClientOptions.PHASE,"runIncremental"])
zinggIncr = EZingg(incrArgs, incrOptions)
zinggIncr.initAndExecute()
```


# Running Zingg On Cloud


# Running On AWS

One option is to use the `spark-submit` option with the Zingg config and phase. Please note that the **config.json** should be available locally at the driver for Zingg to use it.

`````
aws emr create-cluster --name "Add Spark Step Cluster" --release-label emr-6.2.0 --applications Name=Zingg \
--ec2-attributes KeyName=myKey --instance-type <instance type> --instance-count <num instances> \
--steps Type=Spark,Name="Zingg",ActionOnFailure=CONTINUE,Args=[--class,zingg.spark.client.SparkClient,<s3 location of zingg.jar>,--phase,<name of phase - findTrainingData,match etc>,--conf,<local location of config.json>] --use-default-roles````
`````

A step-by-step is provided [here](https://blog.infostrux.com/identity-resolution-with-zingg-ai-snowflake-and-aws-emr-for-the-canadian-football-league-22cf0850ab53). The guide mentions training locally using Zingg Docker, but the **findTrainingData** and **label** phases can be executed on EMR directly.

A second option is to run Zingg Python code in [AWS EMR Notebooks](https://aws.amazon.com/emr/features/notebooks/)


# Running On Azure

To run Zingg on Azure, please refer to [Databricks configuration](/latest/running/databricks).


# Running On Databricks

Step by Step Identity Resolution with Zingg on Databricks

You can run the **Zingg Community Version** on Databricks directly using the Databricks notebook interface.

## For running the Zingg Community version on Databricks:

This uses the Zingg Community Python API and supports Delta tables as well as Unity Catalog (for governed Delta tables) using UCPipe. An [example notebook](https://github.com/zinggAI/zingg/tree/0.7.0/examples/databricks/FebrlExample.ipynb) and a [step by step guide](https://www.zingg.ai/documentation-article/identity-resolution-on-databricks-for-customer-360) is available.

## For running the Zingg Enterprise version on Databricks:

This uses the Zingg Enterprise Python API and all [file formats and data sources and sinks](https://github.com/zinggAI/zingg/tree/0.7.0/docs/dataSourcesAndSinks/README.md) are supported within Databricks with **Zingg Enterprise** including Delta tables, Unity Catalog (for governed Delta tables), and InMemory.


# Running on Fabric

Step by Step Identity Resolution with Zingg on Fabric

You can run the **Zingg Community Version** on Microsoft Fabric directly using the Fabric notebook interface.

## For running the Zingg Community version on Microsoft Fabric:

This uses the Zingg Community Python API and an [example notebook](https://github.com/zinggAI/zingg/tree/0.7.0/examples/fabric/ExampleNotebook.ipynb) and a [step by step guide](https://www.zingg.ai/documentation-article/step-by-step-identity-resolution-with-zingg-on-fabric) is available.

## For running the Zingg Enterprise version on Microsoft Fabric:

This uses the Zingg Enterprise Python API and all [file formats and data sources and sinks](https://github.com/zinggAI/zingg/tree/0.7.0/docs/dataSourcesAndSinks/README.md) are supported within Microsoft Fabric with **Zingg Enterprise** including additional advanced features and support.


# Zingg Models

Zingg learns two models from the data.

## 1. Blocking Model

One fundamental problem with scaling data mastering is that the number of comparisons increases **quadratically** as the number of input records increases.

![Data Mastering At Scale](https://524808071-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGh6QkdUgeylq0gyuMmBw%2Fuploads%2Fgit-blob-d84ed8fee8655cece1f5f5e1d7aa6b81e6d7715f%2Ffuzzymatchingcomparisons.jpg?alt=media)

Zingg learns a clustering/blocking model which indexes near similar records. This means that Zingg does not compare every record with every other record. Typical Zingg comparisons are **0.05-1%** of the possible problem space.

## 2. Similarity Model

The similarity model helps Zingg to predict which record pairs match. The similarity is run only on records within the same block/cluster to scale the problem to larger datasets. The similarity model is a classifier that predicts the similarity of records that are not exactly the same but could belong together.

![Fuzzy matching comparisons](https://524808071-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGh6QkdUgeylq0gyuMmBw%2Fuploads%2Fgit-blob-9c6606491acd13c97cc5c9560919ddebe50c23ba%2FdataMatching.jpg?alt=media)

To build these models, training data is needed. Zingg comes with an interactive learner to rapidly build training sets.

![Shows records and asks user to mark yes, no, cant say on the cli.](https://524808071-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGh6QkdUgeylq0gyuMmBw%2Fuploads%2Fgit-blob-ba27eaba44c1dd5ff8d1760b87107346292ae65d%2Flabel2.gif?alt=media)


# Pre-Trained Models

Zingg comes with pre-trained models for the Febrl, North Carolina Voters, Amazon Google products and few other datasets under the [models](https://github.com/zinggAI/zingg/tree/main/models) folder.

Information on which model is for which dataset is present in [**modelIds.txt**](https://github.com/zinggAI/zingg/blob/main/examples/modelIds.txt) under the models folder.


# Improving Accuracy


# Ignoring Commonly Occuring Words While Matching

Common words like Mr, Pvt, Av, St, Street etc. do not add differential signals and confuse matching. These words are called **stopwords** and matching is more accurate when stopwords are ignored.

The stopwords can be recommended by Zingg by invoking:

`./scripts/zingg.sh --phase recommend --conf <conf.json> --column <name of column to generate stopword recommendations>`

The stopwords generated are stored at the location - models/100/stopWords/columnName. This will give you the list of stopwords along with their frequency.

By default, Zingg extracts 10% of the high-frequency unique words from a dataset. If the user wants a different selection, they should set up the following property in the config file under the respective field for which they want stopwords:

```
stopWordsCutoff: <a value between 0 and 1>
```

Once you have verified the above stop words, you can configure them in the JSON variable **stopWords** with the path to the CSV file containing them. Please ensure while editing the CSV or building it manually that it should contain *one word per row*. Also, ensure that it has a header such as StopWords as Zingg ignores the header by default and works on the remaining data.

```
"fieldDefinition":[
   	{
   		"fieldName" : "fname",
   		"matchType" : "fuzzy",
   		"fields" : "fname",
   		"dataType": "string",
   		"stopWords": "models/100/stopWords/fname.csv"
   	},
```

In case of stopwords being set up manually by the user, the list of stopwords may consider multiple columns and Zingg used only the first column by default.

For recommending stopwords in **Zingg Enterprise Snowflake**,

`./scripts/zingg.sh --phase recommend --conf <conf.json> --properties-file <path to Snowflake properties file> --column <name of column to generate stopword recommendations>`

The stopwords generated are stored in the table - zingg\_stopWords\_columnName\_modelId where we can see the list of stopwords and their frequency associated with the given column name.


# Defining Domain Specific Blocking And Similarity Functions

To add blocking functions and how they work

You can add your own [blocking functions](https://github.com/zinggAI/zingg/tree/main/common/core/src/main/java/zingg/common/core/hash) which will be evaluated by Zingg to build the [blocking tree.](/latest/zmodels)

The blocking tree works on the matched records provided by the user as part of the training. At every node, it selects the hash function and the field on which it should be applied so that there is the least elimination of the matching pairs.\
\
Say we have data like this:

|  Pair 1  | firstname | lastname |
| :------: | :-------: | :------: |
| Record A |    john   |    doe   |
| Record B |   johnh   |   d oe   |

***

|  Pair 2  | firstname | lastname |
| :------: | :-------: | :------: |
| Record A |    mary   |    ann   |
| Record B |   marry   |          |

Let us assume we have hash function **first1char** and we want to check if it is a good function to apply to **firstname**:

| Pair |  Record  | Output |
| :--: | :------: | ------ |
|   1  | Record A | j      |
|   1  | Record B | j      |
|   2  | Record A | m      |
|   2  | Record B | m      |

There is no elimination in the pairs above, hence it is a good function.

Now let us try **last1char** on **firstname:**

| Pair |  Record  | Output |
| :--: | :------: | ------ |
|   1  | Record A | n      |
|   1  | Record B | h      |
|   2  | Record A | y      |
|   2  | Record B | y      |

Pair 1 is getting eliminated above, hence **last1char** is not a good function.

So, **first1char**(**firstname**) will be chosen. This brings near similar records together - in a way, clusters them to break the cartesian join.

These business-specific blocking functions go into [Hash Functions](https://github.com/zinggAI/zingg/tree/main/common/core/src/main/java/zingg/common/core/hash) and must be added to [HashFunctionRegistry](https://github.com/zinggAI/zingg/tree/0.7.0/common/core/src/main/java/zingg/common/core/hash/HashFunctionRegistry.java) and [hash functions config](https://github.com/zinggAI/zingg/tree/0.7.0/common/core/src/main/resources/hashFunctions.json).

Also, for similarity, you can define your own measures. Each **dataType** has predefined features, for example, [String](https://github.com/zinggAI/zingg/tree/0.7.0/common/core/src/main/java/zingg/common/core/feature/StringFeature.java) fuzzy type is configured for Affine and Jaro.

You can define your own [comparisons](https://github.com/zinggAI/zingg/tree/main/common/core/src/main/java/zingg/common/core/similarity/function) and use them.


# Interpreting Output Scores

For each field(**FNAME, NAME, EMAIL** ..), Zingg computes multiple features and feeds them to a classifier. These features are typically different ways to compare strings. We consider string lengths and their differences, character differences, and which characters *actually* differed.

* The shorter string pair ABCD and ABCE will be *less similar* than ABCDEF and ABCEEF.
* Common typos, for e.g. m instead of n, are penalized less severely and will be scored higher than a word with a replaced by non-obvious character b.
* The *more changes* you need to make to the strings to make them match, the less similar they will be.
* Differences in the middle are penalized more than prefixes and suffixes.

No individual feature is perfect, but the whole is greater than the sum of its parts. That's where the strength of Zingg matching and the accuracy comes from.

The classifier finds the best curve to represent the data(features) and gives a final score. This score is dependent on the individual features but is not a linear function but a curve fit. The deterioration in similarity does happen as scores get lower, but we try very hard to find as many possible matches, and hence you will continue to get good quality matches at lower scores. However, the number of good matches at lower scores will be lesser than those at higher scores.

The threshold is automatically chosen so that you can pick up most of the results, with a balance toward accuracy(what is a right match) and recall(not missing out on potential matches). Unlike traditional systems, we do not want the user to worry too much about the cut-off, We optimize the cutoff and hence you may see records with scores less than the conventional 0.5.

A few things to keep in mind while interpreting the scores:

* Matching is *transitive*, so if record A matches Record B and Record B matches C, we put records A, B, and C in the same cluster. That is how different records in a cluster get matched at various confidence levels to the rest and all these records showed up together.
* Our recommendation is to keep a threshold below **x**% of the max score as suspect records - manual review/to be visited later/flow through but will lessen confidence. **x** will depend on how accurate you find the results and how much you want to control the outcome.
* Keep cluster size above 4 or 5 for inspection. You could keep it irrespective of the score, or look at only those clusters whose **z\_minScore** is 0. This will depend on what the results look like to you.


# Explanation of Matches

[Zingg Enterprise Feature](#user-content-fn-1)[^1]

Entity resolution is more effective when you can observe how clusters form, connect, and evolve across runs. Once you have run a `match` or `runIncremental` phase, you may like to look under the hood to understand how a cluster got formed and which records matched with each other and which did not. This insight helps with the following:

* Model Training and Validation: Quickly check if results match expectations.
* Traceability: See exactly how clusters are formed over time.
* Governance: Explain results to business stakeholders.
* Human-in-the-loop Reviews: Share pair-level evidence with domain experts.
* Operational Confidence: Build trust in production pipelines.

## Important Notes

The `explainOutput` phase currently focuses on **probabilistic matches** between records. Please note the following:

* **Deterministic matches** are not included in the explain output at this time. These matches are based on exact rules and can be reviewed separately through your match results.
* Some clusters may not generate explain output, particularly those formed primarily through deterministic matching rules.
* **Empty results** will occur if the specified `ZINGG_ID` does not exist in the cluster data. Please verify the cluster ID exists in your match output before running explain.
* For comprehensive cluster analysis, we recommend reviewing the primary match output alongside the explain output to get the complete picture of both deterministic and probabilistic relationships.

This focused approach allows the explain phase to efficiently highlight the machine learning model's decision-making process for probabilistic matches, which typically require the most scrutiny and validation.

[^1]: Zingg Enterprise is the suite of proprietary products licensed by Zingg. Please refer to <https://www.zingg.ai/product/zingg-entity-resolution-compare-versions> for individual tier features.


# Which records matched with each other in a cluster

Ready to audit a cluster? Plug in the Zingg ID and go. Get pair-level traceability for clusters.

[Zingg Enterprise Feature](#user-content-fn-1)[^1]

Coming Soon!

Many times, records end up being in a cluster even when they may not have directly matched. For example Recod A matched with B, and B matched with C. Some records may match a lot more to other records in the cluster, while one record may just match with one andhence get included in the cluster. If you want to audit clusters, investigate anomalies, present rationale to stakeholders, or refine your matching strategy with confidence, it is important to understand pair wise matching.

The `explain` phase helps us learn this.

## Configuration

The `explain` configuration references the original matching config and defines where to write the results.

```json
{
  "config": "path_to_original_matching_config/config.json",
  "explainOutput": {
    "name": "outputExplain",
    "format": "csv",
    "props": {
      "location": "/tmp/zinggOutput_explain",
      "delimiter": ",",
      "header": true
    }
  }
}  
```

Run the **explain** phase from your Zingg Enterprise Installation and pass the Zingg ID of the cluster you want to inspect:

```
./scripts/zingg.sh --phase explain --zinggid <zingg-id> --conf <path-to-explain-config.json>
```

Example:

```
./scripts/zingg.sh --phase explain \
  --zinggid ea67d79a-56a7-4431-ab55-d08bb3c10e2e \
  --conf ./examples/febrl/explainConfig.json
```

This will provide a pair-level view `(pk1, pk2)` for the selected Zingg ID

* `pk1`: Primary key (record ID) of the first record in the pair
* `pk2`: Primary key (record ID) of the second record in the pair

[^1]: Zingg Enterprise is the suite of proprietary products licensed by Zingg. Please refer to <https://www.zingg.ai/product/zingg-entity-resolution-compare-versions> for individual tier features.


# Output Statistics

Under the hoods of the matching process

[Zingg Enterprise Feature](#user-content-fn-1)[^1]

If you’ve ever asked “how deterministic rules are performing?” or “did my latest incremental run improve cluster quality?”, Output Statistics is your answer. The Output Statistics surface information about the linkages Zingg found among records within a cluster. While running Zingg incrementally, Output Statistics expose how cluster numbers change as records get inserted and updated into the identity graph. Match Statistics surfaces those insights by writing structured metrics for every match or incremental run, so you can:

* See how dense or sparse your clusters are
* Understand how much of a cluster is explained by deterministic rules vs. probabilistic links
* Identify highly central records (connectors) and outliers
* Track how clusters change across runs (growth, splits, merges, reassignments)

If the number of clusters changes disproportionately to the number of records updated or added, an alert could be triggered.

***

## What gets written

Zingg writes statistics to the stats directory whenever you run phases like match or incremental. The output comprises of three types:

* SUMMARY: High-level run summary
* CLUSTER: One row per cluster with cluster level matching metrics
* RECORD: One row per record with its matching metrics within its cluster

[^1]: Zingg Enterprise is the suite of proprietary products licensed by Zingg. Please refer to [Zingg Entity Resolution Compare Versions](https://www.zingg.ai/product/zingg-entity-resolution-compare-versions) for individual tier features.


# Configuring Output Statistics

[Zingg Enterprise Feature](#user-content-fn-1)[^1]

You can configure stats output either via JSON configuration or via the Python API. Please ensure the path/name contains the placeholder “*$ZINGG\_DYNAMIC\_STAT\_NAME*”. This placeholder will get substituted with the statistics type (SUMMARY, CLUSTER, RECORD) and written to respective locations.

### JSON configuration example

```json
{
  "outputStats" : {
       "name":"stats", 
       "format":"csv", 
       "props": {
          "location": "/tmp/zinggStats_$ZINGG_DYNAMIC_STAT_NAME",
          "delimiter": ",",
          "header":true
       }
    }
}
```

### Python configuration example

```python
from zingg import ECsvPipe

statsOutputPipe = ECsvPipe("stats", "/tmp/febrlStats_$ZINGG_DYNAMIC_STAT_NAME")
statsOutputPipe.setHeader("true")
eArgs.setOutputStats(statsOutputPipe)
```

When you execute **match** or **incrementalRun** phases with the output stats pipe, Zingg writes stats for this run under the configured location with timestamp. If the outputStats are not configured, Zingg will not write them and the run will proceed as is.

[^1]: Zingg Enterprise is the suite of proprietary products licensed by Zingg. Please refer to <https://www.zingg.ai/product/zingg-entity-resolution-compare-versions> for individual tier features.


# Cluster Statistics

[Zingg Enterprise Feature](#user-content-fn-1)[^1]

### Cluster Stats

* `z_cluster_count = n`: Number of records in the cluster (cluster size).
* `z_cluster_edges`: Total number of deterministic and probabilistic pairs among records in the cluster. This shows all the pairwise connections formed within the cluster.
* `z_cluster_deterministic_edges`: Total number of deterministic edges among records in the cluster as [configured through the determinstic rules](/latest/stepbystep/configuration/deterministicmatching).
* `z_cluster_centrality = z_cluster_edges / (nC2)`: Edge density of connections in the cluster. Values near 1 → dense clusters (almost all pairs connected); lower values → sparse clusters.
* `z_cluster_determinism = z_cluster_deterministic_edges / (nC2)`: Proportion of connectivity driven by deterministic rules. Higher means more rule-driven cluster cohesion.

Note:

* `nC2` (combinations of n records taken 2 at a time) equals `n*(n-1)/2`.

#### Tip

* Watch determinism ratio: Rising `z_cluster_determinism` over iterations usually means your rules are improving.

[^1]: Zingg Enterprise is the suite of proprietary products licensed by Zingg. Please refer to <https://www.zingg.ai/product/zingg-entity-resolution-compare-versions> for individual tier features.


# Record Statistics

Record level matching explanation

[Zingg Enterprise Feature](#user-content-fn-1)[^1]

### Record Stats

* `z_record_deterministic_edges`: Number of records this record matches deterministically.
* `z_cluster_count = n`: Number of records in the cluster that this record is part of.
* `z_record_edges`: Total number of other records with which this record matches deterministically and probabilistically.
* `z_record_centrality = z_record_edges / (n - 1)`: How central the record is within its cluster.
* `z_record_determinism = z_record_deterministic_edges / (n - 1)`: Proportion of the record’s connectivity explained by deterministic rules.

#### Tip

* Audit connectors: A few records with very high `z_record_centrality` can sometimes falsely bridge clusters so validate them.

[^1]: Zingg Enterprise is the suite of proprietary products licensed by Zingg. Please refer to <https://www.zingg.ai/product/zingg-entity-resolution-compare-versions> for individual tier features.


# Summary Statistics

Displays total number of records, clusters found after each match or incremental run with timestamp


# Combining Different Match Models

When a single match model is not sufficient

[Zingg Enterprise Feature](#user-content-fn-1)[^1]

In many cases, we want to build the identity graph using a combination of different datasets, schemas and matching logic. An example could be having a source system which only contains userids and emails, another one wtih user name and phone numbers and a few others with person information with addresses. Another example could be some systems capturing spousal information, but others to be matched on the basis of lastname and address.

In such cases, Zingg can build the entire graph and relate different models together. In the following case, results of a query with exact match on family Id and a matching model(household) using address and lastname are brought together.

````json
```
{ 
    "vertices" : 
    [ 
        { 
            "name" : "spouse",  
            "vertexType" : "zingg_pipe", 
            "data" : [
                {
                "name" : "spouse", 
                "format" : "snowflake", 
                "props": {
                        "query": "select a.id as id, a.FNAME, a.LNAME, a.STNO, a.ADD1, a.CITY, a.STATE, a.ZINGG_ID_PERSON, b.id as z_id, b.fname as Z_FNAME,b.lname as Z_LNAME,b.stno as Z_STNO,b.add1 as Z_ADD1, b.city as Z_CITY,b.state as Z_STATE, b.ZINGG_ID_PERSON as Z_ZINGG_ID_PERSON from CUSTOMER_RELATE_PARTIAL a, CUSTOMER_RELATE_PARTIAL b where a.familyId = b.familyId"
                        }
                }
                ],
            "edges" :  
            {   "edgeType" : "same_edge",
                "edges":[
                    {
                        "dataColumn" : "zingg_personId",
                        "column" : "zingg_personId",
                        "name" : "zingg_personId1"
                    },
                    {
                        "dataColumn" : "zingg_personId",
                        "column" : "z_zingg_personId",
                        "name" : "zingg_personId2"
                    }
                ]
            }
        },
        { 
            "name" : "household",
            "config" : "$ZINGG_ENTERPRISE_HOME$/zinggEnterprise/configHousehold.json", 
            "strategy" : {
                "vDataStrategy" : "unique_edge",
                "props" : {
                        "column" : "zingg_personId",
                        "edge" : "zingg_personId,z_zingg_personId"
                    }
            },
            "vertexType" : "zingg_match", 
             "edges" :  
            {   "edgeType" : "same_edge",
                "edges":[
                    {
                        "dataColumn" : "zingg_personId",
                        "column" : "zingg_personId",
                        "name" : "zingg_personId1"
                    },
                    {
                        "dataColumn" : "zingg_personId",
                        "column" : "z_zingg_personId",
                        "name" : "zingg_personId2"
                    }
                ]
            }
        }
    ],
    "output" : [{
        "name":"relatedCustomers", 
        "format":"snowflake", 
        "props": {
            "table": "RELATED_CUSTOMERS_PARTIAL"
            }
    }],
    "strategy":"pairs_and_vertices"
}


```
````

[^1]: Zingg Enterprise is the suite of proprietary products licensed by Zingg. Please refer to <https://www.zingg.ai/product/zingg-entity-resolution-compare-versions> for individual tier features.


# Reporting Bugs And Contributing

Want to report a bug or request a feature? Let us know on [Slack](https://join.slack.com/t/zinggai/shared_invite/zt-w7zlcnol-vEuqU9m~Q56kLLUVxRgpOA), or open an [issue](https://github.com/zinggAI/zingg/issues/new/choose).

Want to commit code? Please take a look at the [open issues](https://github.com/zinggAI/zingg/issues) or choose the ones marked [good first issue](https://github.com/zinggAI/zingg/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22+). Let us talk on [Slack](https://join.slack.com/t/zinggai/shared_invite/zt-w7zlcnol-vEuqU9m~Q56kLLUVxRgpOA) if you need help deciding what to fix or how.

As the sponsor of Zingg, Zingg Labs would like to ensure the long-term viability of Zingg and its community. For all code contributions to Zingg, we ask that contributors complete and sign a Contributor License Agreement. The Contributor License Agreement helps ensure everyone can enjoy Zingg with confidence that Zingg is here to stay.

Specifically, our Contributor License Agreement (CLA) grants the contributor and Zingg Labs joint copyright interest in contributed code. Further, it provides assurance from the contributor that contributions are original work that does not violate any third-party license agreement. The agreement between contributors and the Zingg project is explicit. This ensures that Zingg users can be confident in the legal status of the source code and their right to use it.

* [Individual Contributor License Agreement v1.0](https://forms.gle/wU46nGvTMM2wSzy5A)

The above agreement is based on the Contributor License Agreements published by the [Apache Software Foundation](http://www.apache.org), with modifications:

* [Individual Contributor License Agreement ("Agreement") V2.0](http://apache.org/licenses/icla.pdf)


# Setting Up Zingg Development Environment

Welcome to the Zingg development setup guide. To provide you with the most accurate instructions, please select your operating system:

* [**macOS Setup Guide**](/latest/contributing/settingupzingg/macossetup)
* [**Ubuntu / WSL2 Setup Guide**](/latest/contributing/settingupzingg/ubuntusetup)

***

If you're using a different Linux distribution, the Ubuntu instructions will likely be the most relevant, but you may need to adjust package management commands (e.g., using `yum` or `dnf` instead of `apt`).


# macOS Setup Guide

This guide provides specific instructions for setting up the Zingg development environment on macOS.

### **Step 0: Initial OS Setup**

Homebrew is the preferred package manager for macOS and makes installing system dependencies much easier. While you can install dependencies manually, this guide uses Homebrew for simplicity.

Install Homebrew:

```bash
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
```

**Note for Apple Silicon:** If `brew` is not found, run: `echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zshrc && source ~/.zshrc`

***

### **Step 1: Clone The Zingg Repository**

```bash
brew install git
```

**Note:** It is suggested to fork the repository to your account and then clone the repository.

```bash
git clone https://github.com/zinggAI/zingg.git
```

***

### **Step 2: Install JDK 11 (Java Development Kit)**

* Install OpenJDK 11: `brew install openjdk@11`
* Link Java 11:
  * **Apple Silicon:** `sudo ln -sfn /opt/homebrew/opt/openjdk@11/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk-11.jdk`
  * **Intel:** `sudo ln -sfn /usr/local/opt/openjdk@11/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk-11.jdk`

***

### **Step 3: Install Apache Spark**

* Download Apache Spark from the [Apache Spark Official Website](https://spark.apache.org/downloads.html).
* For example for 3.5.0:

```bash
curl -O https://archive.apache.org/dist/spark/spark-3.5.0/spark-3.5.0-bin-hadoop3.tgz
tar -xvf spark-3.5.0-bin-hadoop3.tgz
sudo mv spark-3.5.0-bin-hadoop3 /opt/spark
rm spark-3.5.0-bin-hadoop3.tgz
```

Make sure that Spark version you have installed is compatible with Java you have installed, and Zingg is supporting those versions.

**Note**: Zingg supports Spark 3.5 and the corresponding Java version.

***

### **Step 4: Install Apache Maven**

```bash
brew install maven
```

***

### **Step 5: Update Environment Variables (\~/.zshrc)**

```bash
export JAVA_HOME=/Library/Java/JavaVirtualMachines/openjdk-11.jdk/Contents/Home
export SPARK_HOME=/opt/spark
export SPARK_MASTER=local[*]
export ZINGG_HOME=<path_to_zingg>/assembly/target
export PATH=$PATH:$SPARK_HOME/bin:$SPARK_HOME/sbin:$JAVA_HOME/bin
```

`<path_to_zingg>` will be a directory where you clone the repository of the Zingg. Similarly, if you have installed spark on a different directory you can set **SPARK\_HOME** accordingly.

* Save/exit and do source .zshrc so that they reflect:

```bash
source ~/.zshrc
```

* Verify:

```bash
echo $PATH
mvn --version
```

***

### **Step 6: Compile The Zingg Repository**

* Make sure you are executing the following commands in the same terminal window where you saved the .zshrc. Run the following to compile the Zingg Repository:

```bash
git branch
```

* Run the following to Compile the Zingg Repository:

```bash
mvn initialize
mvn clean compile package -Dspark=sparkVer
```

* Run the following to Compile while skipping tests:

```bash
mvn initialize
mvn clean compile package -Dspark=sparkVer -Dmaven.test.skip=true
```

**Note:** Replace the `sparkVer` with the version of Spark you installed. For example, **-Dspark=3.5**. If you still face an error, include **-Dmaven.test.skip=true** with the above command.

***

### **Step 7: If you have any issue with 'SPARK\_LOCAL\_IP'**

1. **Find your IP:** `ipconfig getifaddr en0`
2. **Find your Hostname:** `hostname`
3. **Update hosts:** `sudo nano /etc/hosts` and add `[your-ip] [your-hostname]` at the bottom.

***

### **Step 8: Run Zingg To Find Training Data**

* Run this script in the terminal opened in Zingg clones directory:

```bash
./scripts/zingg.sh --phase findTrainingData --conf examples/febrl/config.json
```

**If everything is right, it should show Zingg banner.**

***

### **Step 9: Run Zingg To Label Data**

* Run this script in the terminal opened in Zingg clones directory:

```bash
./scripts/zingg.sh --phase label --conf examples/febrl/config.json --properties-file config/zingg.conf
```

***

### **Step 10: Run Zingg To Train Model Based On Labeling**

* Run this script in the terminal opened in Zingg clones directory:

```bash
./scripts/zingg.sh --phase train --conf examples/febrl/config.json --properties-file config/zingg.conf
```

***

### **Step 11: Run Zingg To Prepare Final Output Data**

* Run this script in the terminal opened in Zingg clones directory:

```bash
./scripts/zingg.sh --phase match --conf examples/febrl/config.json --properties-file config/zingg.conf
```

* Change directory `cd /tmp/zinggOutput` (or the path provided in your config file) to see the output files.


# Ubuntu/WSL2 Setup Guide

The following steps will help you set up the Zingg Development Environment on **Ubuntu/WSL2**.

### **Step 0: Initial OS Setup (Ubuntu/WSL2)**

Make sure to update your Ubuntu installation:

`sudo apt update`

**Step 0: Install Ubuntu on WSL2 on Windows**

* Install **wsl**: Type the following command in **Windows PowerShell**.

```
wsl --install
```

* Download Ubuntu from **Microsoft Store**, **Ubuntu 20.04 LTS**
* Configure Ubuntu with a **username** and **password**
* Open **Ubuntu 20.04 LTS** and start working

```
sudo apt update
```

* Follow this [tutorial](https://ubuntu.com/tutorials/install-ubuntu-on-wsl2-on-windows-10#1-overview) for more information.

**Step 1: Clone The Zingg Repository (Ubuntu)**

* Install and SetUp Git: **sudo apt install git**
* Verify : **git --version**
* Set up Git by following the [tutorial](https://www.digitalocean.com/community/tutorials/how-to-install-git-on-ubuntu-20-04).
* Clone the Zingg Repository: **git clone <https://github.com/zinggAI/zingg.git>**

**Note:** It is suggested to fork the repository to your account and then clone the repository.

**Step 2: Install JDK 11 (Ubuntu)**

* Follow this [tutorial](https://linuxize.com/post/install-java-on-ubuntu-20-04/) to install Java 11 JDK 11 in Ubuntu.
* For example:

```
sudo apt install openjdk-11-jdk openjdk-11-jre
javac -version
java -version
```

**Step 3: Install Apache Spark**

#### **Common Steps**

* Download Apache Spark - from the [Apache Spark Official Website](https://spark.apache.org/downloads.html).
* For example for 3.5.0:

```bash
curl -O https://archive.apache.org/dist/spark/spark-3.5.0/spark-3.5.0-bin-hadoop3.tgz
tar -xvf spark-3.5.0-bin-hadoop3.tgz
sudo mv spark-3.5.0-bin-hadoop3 /opt/spark
rm spark-3.5.0-bin-hadoop3.tgz
```

#### **Original Ubuntu Instructions (Manual Wget)**

* Install downloaded Apache Spark - on your Ubuntu by following [this tutorial](https://computingforgeeks.com/how-to-install-apache-spark-on-ubuntu-debian/).
* For example for 3.5.0:

```
wget https://www.apache.org/dyn/closer.lua/spark/spark-3.5.0/spark-3.5.0-bin-hadoop3.tgz
tar -xvf spark-3.5.0-bin-hadoop3.tgz
rm -rf spark-3.5.0-bin-hadoop3.tgz
sudo mv spark-3.5.0-bin-hadoop3 /opt/spark
```

Make sure that Spark version you have installed is compatible with Java you have installed, and Zingg is supporting those versions.

**Note**: Zingg supports Spark 3.5 and the corresponding Java version.

**Step 4: Install Apache Maven (Ubuntu)**

* Install the latest **maven** package.
* For example for 3.8.8:

```
wget https://dlcdn.apache.org/maven/maven-3/3.8.8/binaries/apache-maven-3.8.8-bin.tar.gz
tar -xvf apache-maven-3.8.8-bin.tar.gz 
rm -rf apache-maven-3.8.8-bin.tar.gz 
cd apache-maven-3.8.8/
cd bin
./mvn --version

Make sure that mvn -version should display correct java version as well(JAVA 11)
Apache Maven 3.8.7
Maven home: /usr/share/maven
Java version: 11.0.23, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
```

**Step 5: Update Environment Variables (Ubuntu - \~/.bashrc)**

Open `.bashrc` and add env variables at the end of the file.

```
vim ~/.bashrc
export SPARK_HOME=/opt/spark
export SPARK_MASTER=local[*]
export MAVEN_HOME=/home/ubuntu/apache-maven-3.8.8
export ZINGG_HOME=<path_to_zingg>/assembly/target
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
export PATH=$PATH:$SPARK_HOME/bin:$SPARK_HOME/sbin:$JAVA_HOME/bin

```

\<path\_to\_zingg> will be a directory where you clone the repository of the Zingg. Similarly, if you have installed spark on a different directory you can set **SPARK\_HOME** accordingly.

**Note :-** Skip exporting MAVEN\_HOME if multiple maven version are not required

* Save/exit and do source .bashrc so that they reflect

```
source ~/.bashrc
```

* Verify:

```
echo $PATH
mvn --version

```

**Note:** If you have already set up **JAVA\_HOME** and **SPARK\_HOME** in the steps before you don't need to do this again.

**Step 6: Compile The Zingg Repository**

* Make sure you are executing the following commands in the same terminal window where you saved the bashrc. Run the following to compile the Zingg Repository -

```
git branch

```

* Run the following to Compile the Zingg Repository

```
mvn initialize
mvn clean compile package -Dspark=sparkVer
```

* Run the following to Compile while skipping tests

```
mvn initialize
mvn clean compile package -Dspark=sparkVer -Dmaven.test.skip=true
```

**Note:** Replace the `sparkVer` with the version of Spark you installed.\
For example, **-Dspark=3.5** you still face an error, include **-Dmaven.test.skip=true** with the above command.

**Step 7: If you have any issue with 'SPARK\_LOCAL\_IP' (Ubuntu)**

* Install **net-tools** using **sudo apt-get install -y net-tools**
* Run `ifconfig` in the terminal, find the **IP address** and paste the same in **/opt/hosts** IP address of your Pc-Name

**Step 8: Run Zingg To Find Training Data**

* Run this script in the terminal opened in Zingg clones directory `./scripts/zingg.sh --phase findTrainingData --conf examples/febrl/config.json`

**If everything is right, it should show Zingg banner.**

**Step 9: Run Zingg To label Data**

* Run this script in the terminal opened in Zingg clones directory `./scripts/zingg.sh --phase label --conf examples/febrl/config.json --properties-file config/zingg.conf`

**Step 10: Run Zingg To train model based on labelling**

* Run this script in the terminal opened in Zingg clones directory `./scripts/zingg.sh --phase train --conf examples/febrl/config.json --properties-file config/zingg.conf`

**Step 11: Run Zingg To prepare final output data**

* Run this script in the terminal opened in Zingg clones directory `./scripts/zingg.sh --phase match --conf examples/febrl/config.json --properties-file config/zingg.conf`
* change directory `cd /tmp/zinggOutput` (path provided in config file) to see the output files.


# Community

Be part of the conversation in the [Zingg Community Slack](https://join.slack.com/t/zinggai/shared_invite/zt-w7zlcnol-vEuqU9m~Q56kLLUVxRgpOA).


# Frequently Asked Questions

## How much training is enough?

Typically 30-40 positive pairs (matches) should build a good first model. While marking records through the interactive learner, you can check Zingg's predictions for the shown pair. If they seem to be correct, you can pause and run Zingg in train and match phases to see what result you are getting. If not satisfied, you can always run the findTrainingData and label jobs again and they will pick from the last training round.

## Do I need to train for every new dataset?

No, absolutely not! Train only if the schema(attributes or their types) has changed.

## Do I need to use a Spark cluster or can I run on a single machine?

Depends on the data size you have. Check [hardware sizing](/latest/stepbystep/hardwaresizing) for more details.

## I dont have much background in ML or Spark. Can I still use Zingg ?

Very much! Zingg uses Spark and ML under the hood so that you don't have to worry about the rules and the scale.

## Is Zingg an MDM?

Zingg Community Version is a DIY Master Data Management Product, solving the entity matching and linking process. You can build an MDM in a data store of your choice using Zingg Community. Zingg Enterprise Version is a lakehouse/warehouse native MDM with advance features for globally unique and peristent identifier, gden records, agentic stewardship and survivorship.

## Is Zingg a CDP ?

No, Zingg is not a CDP. However, Zingg is part of the Composable CDP stack, where enterprises assemble customer platform off your warehouse or datalake, Zingg is a great fit. You can leverage existing ETL, observability, and other tools which are part of your data stack and use Zingg for identity.\
Zingg Community Version can be used to build a composable CDP by identity resolution natively on the warehouse and datalake and building customer 360 views. Zingg's identity resolution is far more powerful than what is offered by any out of the box CDP.\
Zingg Enterprise's probabilistic and deterministic matching take this further beyond. Here is an [article](https://hightouch.com/blog/warehouse-identity-resolution/) describing how you can build your own CDP on the warehouse with Zingg.

## I can do Entity Resolution using a graph database like TigerGraph/Neo4J, why do I need Zingg ?

Doing entity resolution in graph databases is easy only if you have trusted and high-quality identifiers like passport id, SSN id, etc. through which edges can be defined between different records. If you need fuzzy matching, you will have to build your own rules and algorithms with thresholds to define matching records. Zingg and Graph Databases go hand in hand for Entity Resolution. It is far easier to use Zingg and persist its graph output to a graph database and do further processing for AML, and KYC scenarios there. Read [the article](https://towardsdatascience.com/entity-resolution-with-tigergraph-add-zingg-to-the-mix-95009471ca02) for details on how Zingg uses TigerGraph for Entity Resolution.


# Reading Material

Entity Resolution and The Modern Data Stack:

* [From Rows to People](https://roundup.getdbt.com/p/from-rows-to-people)

Identity Resolution and Why CDPs fail:

* [Customer Journey](https://www.gartner.com/en/articles/customer-journey)

Entity Resolution using a graph database:

* [Entity Resolution with TigerGraph](https://towardsdatascience.com/entity-resolution-with-tigergraph-add-zingg-to-the-mix-95009471ca02)

A detailed write-up on entity resolution - the problem, its challenges, and applications:

* [Entity Resolution](https://towardsdatascience.com/an-introduction-to-entity-resolution-needs-and-challenges-97fba052dde5)

Understanding Master Data and Master Data Management:

* [Agile Data Mastering](https://towardsdatascience.com/a-guide-to-agile-data-mastering-with-ai-3bf38f103709)


# Security And Privacy

A note about telemetry and product usage data collection in Zingg

Zingg models are built on your data and deployed within your network. No data leaves your environment.

However, Zingg does collect usage metrics and writes them to Google Analytics. This is done to understand and improve the user experience. Please be assured that **Zingg does not capture any input data and will never do so. No input or output data ever leaves any Zingg deployment. The privacy and governance settings of the execution eivironments of Zingg are always enforced due to the native and on-premise deployment of all editions and versions of Zingg. This is valid for the base Operating System, Databricks, Snowflake, Bigquery, Fabirc, Azure, AWS, GCP, Private Data Centre or any other platforms where you run Zingg.**

The following details are captured:

* **Data source type:** data format e.g. CSV, snowflake, parquet
* **Fields count:** number of fields used for training
* **Record Count:** total number of total records pasing through Zingg
* **Execution Time:** execution time of the program
* **Matches and Nonmatches:** the number of matched and nonmatched records in the model
* **Domain:** helps Zingg developers to understand our user enterprises
* **Zingg user id:** Zingg OS User ID
* **JDK information:** version, flavour
* **OS information:** operating system type
* **Databricks Environment:**

An example analytics event while running Zingg is

```
event is {"client_id":"localhost","events":[{"name":"match","params":{"executionTime":"1.743246748E9","country":"IN","zingg_version":"0.5.0","modelId":"100","domain":"localhost","java_version":"11.0.18","os_arch":"aarch64","os_name":"Mac OS X","DB_INSTANCE_TYPE":null,"DATABRICKS_RUNTIME_VERSION":null,"dataCount":"65.0","ZINGG_HOME":"true"}}],"user_id":"zingg"}
```

To view the information captured while running Zingg, edit log4j2.properties and turn loggin level of zingg\_analytics to warn

```
logger.zingg_analytics.level = warn
```

If you do not wish to send the data, please set collectMetrics flag to false in the configuration JSON while running Zingg. A blank event will be logged in this case

```
{"client_id":"localhost","events":[{"name":"train"}],"user_id":"zingg"}
```


