![license](https://img.shields.io/github/license/MediaCompLab/CIGA.svg)
![package](https://github.com/MediaCompLab/CIGA/actions/workflows/python-package.yml/badge.svg?event=push)
![publish](https://github.com/MediaCompLab/CIGA/actions/workflows/python-publish.yml/badge.svg)

# CIGA: Character Interaction Graph Analyzer

CIGA is a Python package designed for performing graph analysis on social interactions between individuals across time.
It is a redesign of [CharNet](https://github.com/MediaCompLab/CharNet) using igraph.

- **Github:** https://github.com/MediaCompLab/CIGA

## Simple example

---

```python
import ciga as cg
import pandas as pd
import matplotlib.pyplot as plt

df = pd.DataFrame({
        'Season': [1, 1, 1, 1],
        'Episode': [1, 1, 1, 1],
        'Scene': [1, 1, 2, 2],
        'Line': [1, 2, 1, 2],
        'Speaker': ['Sheldon', 'Leonard', 'Penny', 'Sheldon'],
        'Listener': ['Leonard', 'Sheldon', 'Sheldon', 'Penny'],
        'Words': ['Hello', 'Hi there', 'How are you?', 'Fine, thank you']
    })

def weight_func(interaction):
    return 1

position = ('Season', 'Episode', 'Scene', 'Line')
interactions = cg.prepare_data(data=df,
                               position=position,
                               source='Speaker', 
                               target='Listener', 
                               interaction='Words')

sub_interactions = cg.segment(interactions, start=(1, 1, 1, 1), end=(2, 1, 1, 1))
weights = cg.calculate_weights(sub_interactions, weight_func)
agg_weights = cg.agg_weights(data=weights, 
                             position=position[:-1], 
                             agg_func=lambda x: sum(x))

tg = cg.TGraph(data=agg_weights, 
               position=position[:-1], 
               directed=False)

graph = tg.get_graph((1, 1, 1))
fig, ax = plt.subplots()
cg.iplot(graph, target=ax)
plt.show()

res = cg.tgraph_degree(tg, weighted=True, w_normalized=False, normalized=True)

res.to_csv('results.csv')
```

## More Examples

---

### 1. Basic Interaction Graph Creation and Visualization

```python
import ciga as cg
import pandas as pd
import matplotlib.pyplot as plt

# Sample interaction data
data = pd.DataFrame({
    'Time': [1, 1, 2, 2, 3],
    'Source': ['Alice', 'Bob', 'Alice', 'Charlie', 'Bob'],
    'Target': ['Bob', 'Alice', 'Charlie', 'Alice', 'Charlie'],
    'Interaction': ['talk', 'talk', 'nod', 'talk', 'smile']
})

# Prepare the data
position = ('Time',)
interactions = cg.prepare_data(data, position, source='Source', target='Target', interaction='Interaction')

# Calculate weights (using the length of the interaction as weight)
weights = cg.calculate_weights(interactions, weight_func=lambda x: len(x))

# Aggregate weights
agg_weights = cg.agg_weights(weights, position)

# Create a temporal graph
tg = cg.TGraph(data=agg_weights, position=position, directed=True)

# Get the graph at time step 2
graph = tg.get_graph(time_point=(2,))

# Visualize the graph
fig, ax = plt.subplots()
cg.iplot(graph, target=ax)
plt.show()
```

### 2. Centrality Analysis

```python
import ciga as cg
import pandas as pd

# ... (using the same 'data' and 'tg' from the previous example)

# Degree centrality
degree_centrality = cg.tgraph_degree(tg, weighted=True, normalized=True)
print("Degree Centrality:\n", degree_centrality)

# Betweenness centrality
betweenness_centrality = cg.tgraph_betweenness(tg, weighted=True, normalized=True)
print("Betweenness Centrality:\n", betweenness_centrality)

# Closeness centrality
closeness_centrality = cg.tgraph_closeness(tg, weighted=True, normalized=True)
print("Closeness Centrality:\n", closeness_centrality)

# Eigenvector centrality
eigenvector_centrality = cg.tgraph_eigenvector_centrality(tg, weighted=True)
print("Eigenvector Centrality:\n", eigenvector_centrality)
```

### 3. Community Detection

```python
import ciga as cg
import pandas as pd

# ... (using the same 'data' and 'tg' from the previous example)

# Community detection using Leiden algorithm
communities = cg.tgraph_community_leiden(tg, weights='weight', resolution=1.0)
print("Communities:\n", communities)
```

### 4. Graph Properties

```python
import ciga as cg
import pandas as pd

# ... (using the same 'data' and 'tg' from the previous example)

# Graph density over time
density = cg.tgraph_density(tg)
print("Density:\n", density)

# Graph transitivity over time
transitivity = cg.tgraph_transitivity_undirected(tg)
print("Transitivity:\n", transitivity)
```

### 5. Using a Custom Weight Function

```python
import ciga as cg
import pandas as pd

# Sample interaction data with text
data = pd.DataFrame({
    'Time': [1, 1, 2, 2, 3],
    'Source': ['Alice', 'Bob', 'Alice', 'Charlie', 'Bob'],
    'Target': ['Bob', 'Alice', 'Charlie', 'Alice', 'Charlie'],
    'Interaction': ['I like you', 'Thanks!', 'This is great', 'Hello', 'Nice to see you']
})

# Custom weight function: assign higher weight to "positive" interactions
def custom_weight_func(interaction):
    positive_words = ['like', 'great', 'nice', 'thanks']
    text = str(interaction).lower()
    score = 1
    for word in positive_words:
        if word in text:
            score += 2
    return score

# Prepare the data
position = ('Time',)
interactions = cg.prepare_data(data, position, source='Source', target='Target', interaction='Interaction')

# Calculate weights using the custom weight function
weights = cg.calculate_weights(interactions, weight_func=custom_weight_func)

# Aggregate weights
agg_weights = cg.agg_weights(weights, position)

# Create a temporal graph
tg = cg.TGraph(data=agg_weights, position=position, directed=True)

# Get the graph at time step 2
graph = tg.get_graph(time_point=(2,))

# Visualize the graph
fig, ax = plt.subplots()
cg.iplot(graph, target=ax)
plt.show()
```

### 6. Inferring Listeners with LLM

```python
import ciga as cg
import pandas as pd
from openai import OpenAI

# Sample data with dialogue and scene descriptions
data = pd.DataFrame({
    'Season': [1, 1, 1, 1, 1],
    'Episode': [1, 1, 1, 2, 2],
    'Scene': [1, 1, 2, 1, 1],
    'Line': [1, 2, 3, 1, 2],
    'Speaker': ['Alice', 'Bob', 'Charlie', 'Alice', 'Bob'],
    'Dialogue': ['Hi Bob', 'Hello Alice', 'Hey everyone', 'Good morning', 'Morning Alice'],
    'Action': ['Waive hand', 'Smile', '', 'Smile', 'Waive hand'],
    'Scene_Description': ['In the room', 'In the room', 'In the room', 'In the room', 'In the room']
})

# Initialize OpenAI-compatible client
client = OpenAI(
    base_url="https://api.openai.com/v1", # Or other providers like https://api.deepseek.com
    api_key="YOUR_API_KEY"
)

# Infer listeners
inferred_listeners = cg.infer_listeners(data=data,
                                        position=('Season', 'Episode', 'Scene', 'Line'),
                                        speaker='Speaker',
                                        dialogue='Dialogue',
                                        action='Action',
                                        scene_description='Scene_Description',
                                        client=client,
                                        model='gpt-4o-mini', # or any compatible model name
                                        max_tokens=200,
                                        gap=0.5)

print("Inferred Listeners:\n", inferred_listeners)
```

### 7. Visualizing with Pyvis

```python
import ciga as cg
import pandas as pd

# ... (using the same 'data' and 'tg' from the previous example)

# Get the graph at time step 2
graph = tg.get_graph(time_point=(2,))

# Visualize the graph using Pyvis
cg.pyviz(graph, output_file='interactive_graph.html')
```

### 8. Topological Data Analysis (TDA)

Analyze the topological stability of the graph over time using Persistent Homology.

```python
import ciga as cg

# ... (using the same 'tg' from the previous example)

# Compute Persistence Diagrams (H0 and H1)
diagrams = cg.tgraph_persistence_diagrams(tg, maxdim=1)

# Calculate Stability (Bottleneck distance) across time
stability = cg.tgraph_stability(tg, metric='bottleneck')
print(stability)
```

## Install

---

Install the latest version of CIGA:

```bash
$ pip install ciga
```

Install with visualization dependencies (for interactive plotting with `pyviz`):
```bash
$ pip install ciga[visualization]
```

Install with TDA dependencies (for topological analysis):
```bash
$ pip install ciga[tda]
```

Install with all optional dependencies:
```bash
$ pip install ciga[all]
```

**Note:** The `pyviz` function requires `pyvis` as an optional dependency. If you try to use `pyviz` without installing the visualization dependencies, you'll see a helpful error message with installation instructions.

## To Do
- [x] Add non-directed graph support
- [x] Add closeness centrality
- [x] Add Eigenvector centrality
- [x] Add Leiden community detection
- [x] Add forgetting simulation
- [x] Add temporal visualization
- [x] Add Topological Data Analysis (Persistent Homology)
- [ ] Add centrality visualizer (with visualization)

## License

Released under the [GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.en.html).

```
Copyright (c) 2024 Media Comprehension Lab
```
