# Deduplication
> **Advanced entity deduplication and resolution system for maintaining a clean, single-source-of-truth Knowledge Graph.**
---
## 🎯 Overview
- :material-content-duplicate:{ .lg .middle } **Duplicate Detection**
---
Identify duplicates using multi-factor similarity metrics
- :material-set-merge:{ .lg .middle } **Entity Merging**
---
Merge entities with configurable strategies (Keep First, Most Complete, etc.)
- :material-group:{ .lg .middle } **Clustering**
---
Cluster similar entities for efficient batch processing
- :material-calculator:{ .lg .middle } **Similarity Metrics**
---
Levenshtein, Jaro-Winkler, Cosine, and Jaccard similarity support
- :material-history:{ .lg .middle } **Provenance**
---
Preserve data lineage and history during merges
- :material-scale:{ .lg .middle } **Scalable**
---
Batch processing and blocking for large datasets
!!! tip "When to Use"
- **Data Ingestion**: Clean incoming data before adding to the graph
- **Graph Maintenance**: Periodically clean up existing knowledge graphs
- **Entity Resolution**: Resolve entities from different sources (e.g., "Apple" vs "Apple Inc.")
---
## ⚙️ Algorithms Used
### Similarity Calculation
The deduplication system uses multiple similarity metrics:
- **Levenshtein Distance**: Edit distance for string difference
- **Jaro-Winkler**: String similarity with prefix weighting (Default for strings, optimized for entity names)
- **Cosine Similarity**: Vector similarity for embeddings
- **Jaccard Similarity**: Set overlap for properties/relationships
- **Property Matching**: Handles disjoint properties with neutral scoring (`` `0.5` ``) to prevent false negatives
- **Multi-factor Aggregation**: Weighted sum of multiple metrics
### Default Configuration
The deduplication module uses the following default weights to prioritize name matching while considering other factors:
- **String Similarity**: `` `0.6` `` (Primary factor, using Jaro-Winkler)
- **Property Similarity**: `` `0.2` `` (Handles missing values neutrally)
- **Relationship Similarity**: `` `0.2` ``
- **Embedding Similarity**: `` `0.0` `` (Optional, enabled if embeddings are present)
### Duplicate Detection
The system uses efficient detection algorithms:
- **Pairwise Comparison**: `` `O(n²)` `` comparison (for small sets)
- **Blocking/Indexing**: Reduce search space for large sets
- **Union-Find**: Disjoint set data structure for grouping duplicates
- **Confidence Scoring**: `` `0.0 - 1.0` `` probability score for duplicates
### Clustering
The module provides clustering algorithms for grouping similar entities:
- **Hierarchical Clustering**: Agglomerative bottom-up clustering
- **Connected Components**: Graph-based cluster detection
- **Cluster Quality**: Cohesion and separation metrics
### Entity Merging
- **Strategy Pattern**: Pluggable merge logic
- **Property Union**: Combining unique properties
- **Relationship Merging**: Re-linking relationships to the merged entity
---
## Main Classes
### DuplicateDetector
Identifies potential duplicates in a dataset using similarity metrics and confidence scoring.
**Initialization:**
```python
DuplicateDetector(
similarity_threshold: float = 0.7,
confidence_threshold: float = 0.6,
use_clustering: bool = True,
config: Optional[Dict[str, Any]] = None,
**kwargs
)
```
**Methods:**
| Method | Description | Returns |
|--------|-------------|---------|
| `detect_duplicates(entities, threshold=None, **options)` | Find duplicate pairs | `List[DuplicateCandidate]` |
| `detect_duplicate_groups(entities, threshold=None, **options)` | Find clusters of duplicates | `List[DuplicateGroup]` |
| `incremental_detect(new_entities, existing_entities, threshold=None, **options)` | Detect duplicates between new and existing entities | `List[DuplicateCandidate]` |
| `detect_relationship_duplicates(relationships, **options)` | Detect duplicate relationships | `List[Tuple[Dict, Dict]]` |
**Example:**
```python
from semantica.deduplication import DuplicateDetector
detector = DuplicateDetector(similarity_threshold=0.85, confidence_threshold=0.7)
candidates = detector.detect_duplicates(entities)
groups = detector.detect_duplicate_groups(entities)
for candidate in candidates:
print(f"Duplicate: {candidate.entity1['name']} <-> {candidate.entity2['name']}")
print(f" Similarity: {candidate.similarity_score:.2f}, Confidence: {candidate.confidence:.2f}")
```
### EntityMerger
Merges duplicate entities into a single canonical entity using configurable strategies.
**Initialization:**
```python
EntityMerger(
preserve_provenance: bool = True,
config: Optional[Dict[str, Any]] = None,
**kwargs
)
```
**Methods:**
| Method | Description | Returns |
|--------|-------------|---------|
| `merge_duplicates(entities, strategy=None, **options)` | Execute merge on duplicate entities | `List[MergeOperation]` |
| `merge_entity_group(entities, strategy=None, **options)` | Merge a specific group of entities | `MergeOperation` |
| `incremental_merge(new_entities, existing_entities, **options)` | Incrementally merge new entities with existing ones | `List[MergeOperation]` |
| `get_merge_history()` | Get merge operation history | `List[MergeOperation]` |
| `validate_merge_quality(merge_operation)` | Validate quality of a merge operation | `Dict[str, Any]` |
**Strategies:**
- `"keep_first"`: Keep the first entity encountered
- `"keep_last"`: Keep the last entity encountered
- `"keep_most_complete"`: Keep entity with most properties/relationships
- `"keep_highest_confidence"`: Keep entity with highest confidence score
- `"merge_all"`: Create new entity combining all info
**Example:**
```python
from semantica.deduplication import EntityMerger
merger = EntityMerger(preserve_provenance=True)
# Merge with specific strategy
operations = merger.merge_duplicates(
entities,
strategy="keep_most_complete"
)
# Get merge history
history = merger.get_merge_history()
```
### SimilarityCalculator
Calculates multi-factor similarity between entities using string, property, relationship, and embedding similarity.
**Initialization:**
```python
SimilarityCalculator(
embedding_weight: float = 0.4,
string_weight: float = 0.3,
property_weight: float = 0.2,
relationship_weight: float = 0.1,
similarity_threshold: float = 0.7,
config: Optional[Dict[str, Any]] = None,
**kwargs
)
```
**Methods:**
| Method | Description | Returns |
|--------|-------------|---------|
| `calculate_similarity(entity1, entity2, **options)` | Calculate overall similarity | `SimilarityResult` |
| `calculate_string_similarity(str1, str2, method="levenshtein")` | Calculate string similarity | `float` |
| `calculate_property_similarity(entity1, entity2)` | Calculate property similarity | `float` |
| `calculate_relationship_similarity(entity1, entity2)` | Calculate relationship similarity | `float` |
| `calculate_embedding_similarity(embedding1, embedding2)` | Calculate embedding similarity | `float` |
| `batch_calculate_similarity(entities, threshold=None)` | Calculate similarity for all pairs | `List[Tuple[Dict, Dict, float]]` |
**Example:**
```python
from semantica.deduplication import SimilarityCalculator
calculator = SimilarityCalculator(
string_weight=0.4,
property_weight=0.3,
embedding_weight=0.3
)
result = calculator.calculate_similarity(entity1, entity2)
print(f"Similarity: {result.score:.2f}")
print(f"Components: {result.components}")
```
### ClusterBuilder
Builds clusters of similar entities for efficient batch deduplication.
**Initialization:**
```python
ClusterBuilder(
similarity_threshold: float = 0.7,
min_cluster_size: int = 2,
max_cluster_size: int = 100,
use_hierarchical: bool = False,
config: Optional[Dict[str, Any]] = None,
**kwargs
)
```
**Methods:**
| Method | Description | Returns |
|--------|-------------|---------|
| `build_clusters(entities, **options)` | Build clusters of similar entities | `ClusterResult` |
| `update_clusters(existing_clusters, new_entities, **options)` | Incrementally update clusters with new entities | `ClusterResult` |
**Example:**
```python
from semantica.deduplication import ClusterBuilder
builder = ClusterBuilder(
similarity_threshold=0.8,
min_cluster_size=2,
max_cluster_size=50
)
result = builder.build_clusters(entities)
print(f"Found {len(result.clusters)} clusters")
print(f"Unclustered: {len(result.unclustered)} entities")
print(f"Quality metrics: {result.quality_metrics}")
```
### MergeStrategyManager
Manages merge strategies and property-specific merge rules with conflict resolution.
**Initialization:**
```python
MergeStrategyManager(
default_strategy: str = "keep_most_complete",
config: Optional[Dict[str, Any]] = None,
**kwargs
)
```
**Methods:**
| Method | Description | Returns |
|--------|-------------|---------|
| `merge_entities(entities, strategy=None, **options)` | Merge entities using specified strategy | `MergeResult` |
| `add_property_rule(property_name, strategy, conflict_resolution=None, priority=0)` | Add property-specific merge rule | `None` |
| `validate_merge(merge_result)` | Validate merge result quality | `Dict[str, Any]` |
**Example:**
```python
from semantica.deduplication import MergeStrategyManager, MergeStrategy
manager = MergeStrategyManager(default_strategy="keep_most_complete")
manager.add_property_rule("name", MergeStrategy.KEEP_FIRST)
manager.add_property_rule("description", MergeStrategy.MERGE_ALL)
result = manager.merge_entities(entities)
print(f"Merged entity: {result.merged_entity}")
print(f"Conflicts: {len(result.conflicts)}")
```
### MethodRegistry
Registry for custom deduplication methods, enabling extensibility.
**Methods:**
| Method | Description | Returns |
|--------|-------------|---------|
| `register(task, name, method_func)` | Register a custom deduplication method | `None` |
| `get(task, name)` | Get method by task and name | `Optional[Callable]` |
| `list_all(task=None)` | List all registered methods | `Dict[str, List[str]]` |
| `unregister(task, name)` | Unregister a method | `None` |
| `clear(task=None)` | Clear all registered methods | `None` |
**Example:**
```python
from semantica.deduplication.registry import method_registry
# Register custom similarity method
def custom_similarity(entity1, entity2, **kwargs):
# Custom logic
return SimilarityResult(score=0.85, method="custom")
method_registry.register("similarity", "custom_method", custom_similarity)
# Use custom method
method = method_registry.get("similarity", "custom_method")
result = method(entity1, entity2)
```
### DeduplicationConfig
Configuration manager for deduplication operations, supporting environment variables, config files, and programmatic configuration.
**Initialization:**
```python
DeduplicationConfig(config_file: Optional[str] = None)
```
**Methods:**
| Method | Description | Returns |
|--------|-------------|---------|
| `set(key, value)` | Set configuration value programmatically | `None` |
| `get(key, default=None)` | Get configuration value | `Any` |
| `set_method_config(method, **config)` | Set method-specific configuration | `None` |
| `get_method_config(method)` | Get method-specific configuration | `Dict` |
| `get_all()` | Get all configuration | `Dict[str, Any]` |
**Example:**
```python
from semantica.deduplication.config import dedup_config
# Get configuration
threshold = dedup_config.get("similarity_threshold", default=0.7)
# Set configuration
dedup_config.set("similarity_threshold", 0.8)
# Method-specific configuration
dedup_config.set_method_config("levenshtein", case_sensitive=False)
levenshtein_config = dedup_config.get_method_config("levenshtein")
```
---
## Data Classes
### DuplicateCandidate
Represents a duplicate candidate pair with confidence scores.
**Fields:**
- `entity1`: First entity dictionary
- `entity2`: Second entity dictionary
- `similarity_score`: Similarity score (0.0 to 1.0)
- `confidence`: Confidence score (0.0 to 1.0)
- `reasons`: List of reasons why they're considered duplicates
- `metadata`: Additional metadata dictionary
### DuplicateGroup
Represents a group of duplicate entities.
**Fields:**
- `entities`: List of duplicate entity dictionaries
- `similarity_scores`: Dict mapping entity pairs to similarity scores
- `representative`: Representative entity (most complete)
- `confidence`: Group confidence score (0.0 to 1.0)
- `metadata`: Additional group metadata
### MergeOperation
Represents an entity merge operation.
**Fields:**
- `source_entities`: List of original entities that were merged
- `merged_entity`: Resulting merged entity dictionary
- `merge_result`: Detailed merge result with conflicts
- `timestamp`: Optional timestamp of merge operation
- `metadata`: Additional operation metadata
### SimilarityResult
Represents a similarity calculation result.
**Fields:**
- `score`: Overall similarity score (0.0 to 1.0)
- `method`: Calculation method used
- `components`: Dict of individual component scores
- `metadata`: Additional metadata dictionary
### Cluster
Represents an entity cluster.
**Fields:**
- `cluster_id`: Unique cluster identifier
- `entities`: List of entities in the cluster
- `centroid`: Optional representative entity (centroid)
- `quality_score`: Cluster quality score (0.0 to 1.0)
- `metadata`: Additional cluster metadata
### ClusterResult
Represents the result of cluster building.
**Fields:**
- `clusters`: List of Cluster objects
- `unclustered`: List of entities not in any cluster
- `quality_metrics`: Cluster quality metrics dictionary
- `metadata`: Additional result metadata
### MergeResult
Represents the result of a merge operation.
**Fields:**
- `merged_entity`: Resulting merged entity dictionary
- `merged_entities`: List of original entities that were merged
- `conflicts`: List of unresolved conflicts
- `metadata`: Additional merge metadata
### PropertyMergeRule
Represents a rule for merging specific properties.
**Fields:**
- `property_name`: Property name
- `strategy`: Merge strategy to use
- `conflict_resolution`: Optional custom conflict resolution function
- `priority`: Rule priority (higher priority rules take precedence)
### MergeStrategy
Enumeration of available merge strategies.
**Values:**
- `KEEP_FIRST`: Keep the first entity encountered
- `KEEP_LAST`: Keep the last entity encountered
- `KEEP_MOST_COMPLETE`: Keep entity with most properties/relationships
- `KEEP_HIGHEST_CONFIDENCE`: Keep entity with highest confidence score
- `MERGE_ALL`: Create new entity combining all info
- `CUSTOM`: Use custom merge logic
---
## Convenience Functions
### detect_duplicates
Convenience function for duplicate detection with multiple methods.
```python
from semantica.deduplication.methods import detect_duplicates
# Pairwise detection
candidates = detect_duplicates(
entities,
method="pairwise",
similarity_threshold=0.8,
confidence_threshold=0.7
)
# Group detection
groups = detect_duplicates(
entities,
method="group",
similarity_threshold=0.8
)
# Incremental detection
new_candidates = detect_duplicates(
new_entities,
method="incremental",
existing_entities=existing_entities,
similarity_threshold=0.8
)
```
**Methods:**
- `"pairwise"`: O(n²) comparison of all entity pairs
- `"batch"`: Efficient batch similarity calculation
- `"incremental"`: O(n×m) comparison for new vs existing entities
- `"group"`: Union-find algorithm for duplicate group formation
### merge_entities
Convenience function for entity merging with multiple strategies.
```python
from semantica.deduplication.methods import merge_entities
operations = merge_entities(
duplicate_entities,
method="keep_most_complete",
preserve_provenance=True
)
```
**Methods:**
- `"keep_first"`: Preserve first entity, merge others
- `"keep_last"`: Preserve last entity, merge others
- `"keep_most_complete"`: Preserve entity with most properties/relationships
- `"keep_highest_confidence"`: Preserve entity with highest confidence
- `"merge_all"`: Combine all properties and relationships
### calculate_similarity
Convenience function for similarity calculation with multiple methods.
```python
from semantica.deduplication.methods import calculate_similarity
# Different similarity methods
exact_result = calculate_similarity(entity1, entity2, method="exact")
lev_result = calculate_similarity(entity1, entity2, method="levenshtein")
jaro_result = calculate_similarity(entity1, entity2, method="jaro_winkler")
multi_result = calculate_similarity(entity1, entity2, method="multi_factor")
```
**Methods:**
- `"exact"`: Exact string matching
- `"levenshtein"`: Levenshtein distance-based similarity
- `"jaro_winkler"`: Jaro-Winkler similarity with prefix bonus
- `"cosine"`: Cosine similarity for embeddings
- `"property"`: Property value comparison
- `"relationship"`: Jaccard similarity of relationships
- `"embedding"`: Cosine similarity of vector embeddings
- `"multi_factor"`: Weighted aggregation of all components
### build_clusters
Convenience function for cluster building with multiple methods.
```python
from semantica.deduplication.methods import build_clusters
# Graph-based clustering
result = build_clusters(
entities,
method="graph_based",
similarity_threshold=0.8
)
# Hierarchical clustering
result = build_clusters(
entities,
method="hierarchical",
similarity_threshold=0.8
)
```
**Methods:**
- `"graph_based"`: Union-find algorithm for connected components
- `"hierarchical"`: Agglomerative clustering for large datasets
### get_deduplication_method
Get deduplication method by task and name.
```python
from semantica.deduplication.methods import get_deduplication_method
method = get_deduplication_method("similarity", "levenshtein")
if method:
result = method(entity1, entity2)
```
### list_available_methods
List all available deduplication methods.
```python
from semantica.deduplication.methods import list_available_methods
# List all methods
all_methods = list_available_methods()
# List methods for specific task
similarity_methods = list_available_methods("similarity")
```
---
## Configuration
The deduplication module supports multiple configuration sources: environment variables, config files (YAML, JSON, TOML), and programmatic configuration.
### Environment Variables
```bash
export DEDUP_SIMILARITY_THRESHOLD=0.8
export DEDUP_CONFIDENCE_THRESHOLD=0.7
export DEDUP_USE_CLUSTERING=true
export DEDUP_PRESERVE_PROVENANCE=true
export DEDUP_DEFAULT_STRATEGY=keep_most_complete
export DEDUP_MIN_CLUSTER_SIZE=2
export DEDUP_MAX_CLUSTER_SIZE=100
```
### YAML Configuration
```yaml
deduplication:
similarity_threshold: 0.8
confidence_threshold: 0.7
use_clustering: true
preserve_provenance: true
default_strategy: keep_most_complete
min_cluster_size: 2
max_cluster_size: 100
deduplication_methods:
levenshtein:
case_sensitive: false
multi_factor:
string_weight: 0.4
property_weight: 0.3
embedding_weight: 0.3
```
### Programmatic Configuration
```python
from semantica.deduplication.config import dedup_config
# Set configuration values
dedup_config.set("similarity_threshold", 0.8)
dedup_config.set("confidence_threshold", 0.7)
# Get configuration values
threshold = dedup_config.get("similarity_threshold", default=0.7)
# Method-specific configuration
dedup_config.set_method_config("levenshtein", case_sensitive=False)
levenshtein_config = dedup_config.get_method_config("levenshtein")
# Load from config file
from semantica.deduplication.config import DeduplicationConfig
config = DeduplicationConfig(config_file="config.yaml")
```
### Configuration File Support
The `DeduplicationConfig` class supports loading configuration from:
- **YAML files** (`.yaml`, `.yml`)
- **JSON files** (`.json`)
- **TOML files** (`.toml`)
Configuration is loaded in the following priority order:
1. Programmatic configuration (via `set()`)
2. Environment variables
3. Config file values
4. Default values
---
## Integration Examples
### Ingestion Pipeline
```python
from semantica.core import Semantica
from semantica.ingest import FileIngestor
from semantica.parse import DocumentParser
from semantica.semantic_extract import NERExtractor
from semantica.deduplication import DuplicateDetector, EntityMerger, MergeStrategy
# 1. Build knowledge base using individual modules
ingestor = FileIngestor()
parser = DocumentParser()
ner = NERExtractor()
all_entities = []
for file_path in files:
doc = ingestor.ingest_file(file_path)
parsed = parser.parse_document(file_path)
text = parsed.get("full_text", "")
entities = ner.extract_entities(text)
all_entities.extend(entities)
raw_entities = all_entities
# 2. Deduplicate
detector = DuplicateDetector(similarity_threshold=0.85)
duplicate_groups = detector.detect_duplicate_groups(raw_entities)
merger = EntityMerger(preserve_provenance=True)
merge_operations = merger.merge_duplicates(
raw_entities,
strategy=MergeStrategy.MERGE_ALL
)
# Extract merged entities
merged_entities = [op.merged_entity for op in merge_operations]
```
---
## Best Practices
1. **Block First**: For >1000 entities, enable blocking to avoid O(n²) performance.
2. **Tune Thresholds**: Start with 0.85 and adjust based on false positive/negative rates.
3. **Preserve Provenance**: Keep `preserve_provenance=True` to track where merged data came from.
4. **Normalize**: Run `normalize` module before deduplication for best results.
---
## Troubleshooting
**Issue**: Merging "Apple" and "Apple Pie" (False Positive).
**Solution**: Increase threshold or use Jaro-Winkler which penalizes prefix mismatches.
```python
detector = DuplicateDetector(
similarity_method="jaro_winkler",
similarity_threshold=0.9
)
```
**Issue**: Slow performance on large datasets.
**Solution**: Use `ClusterBuilder` with blocking.
---
## See Also
- [Conflicts Module](conflicts.md) - Handling conflicting values during merge
- [Normalize Module](normalize.md) - Pre-processing for better matching
- [Knowledge Graph Module](kg.md) - Target for deduplicated data
## Cookbook
Interactive tutorials to learn deduplication:
- **[Deduplication](https://github.com/Hawksight-AI/semantica/blob/main/cookbook/introduction/18_Deduplication.ipynb)**: Advanced deduplication techniques for entity resolution
- **Topics**: Entity deduplication, fuzzy matching, similarity thresholds, merge strategies
- **Difficulty**: Intermediate
- **Use Cases**: Entity resolution, data cleaning, multi-source integration