Spaces:
Sleeping
Sleeping
Yoon-gu Hwang Claude commited on
Commit Β·
7ab1194
1
Parent(s): 3ab49ae
Change visualization to bar chart with binary classification
Browse files- Update drift_simulator.py: Generate binary classification data (class 0 vs class 1)
- Update visualizer.py: Create bar chart visualization instead of scatter plot
- Update analyzer.py: Analyze binary classification data
- Add test files to .gitignore
π€ Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- .gitignore +2 -0
- analyzer.py +59 -29
- drift_simulator.py +30 -33
- visualizer.py +119 -68
.gitignore
CHANGED
|
@@ -15,3 +15,5 @@ build/
|
|
| 15 |
.vscode/
|
| 16 |
*.log
|
| 17 |
flagged/
|
|
|
|
|
|
|
|
|
| 15 |
.vscode/
|
| 16 |
*.log
|
| 17 |
flagged/
|
| 18 |
+
*.html
|
| 19 |
+
test_*.py
|
analyzer.py
CHANGED
|
@@ -11,11 +11,21 @@ def analyze_drift(X: np.ndarray, y: np.ndarray, drift_points: np.ndarray, drift_
|
|
| 11 |
"drift_locations": drift_points.tolist() if len(drift_points) > 0 else [],
|
| 12 |
}
|
| 13 |
|
| 14 |
-
#
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
# μΈκ·Έλ¨ΌνΈλ³ λΆμ
|
| 21 |
segments = []
|
|
@@ -26,26 +36,31 @@ def analyze_drift(X: np.ndarray, y: np.ndarray, drift_points: np.ndarray, drift_
|
|
| 26 |
end = segment_boundaries[i + 1]
|
| 27 |
|
| 28 |
segment_y = y[start:end]
|
| 29 |
-
segment_X = X[start:end]
|
| 30 |
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
else:
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
|
|
|
|
|
|
| 49 |
|
| 50 |
analysis["segments"] = segments
|
| 51 |
|
|
@@ -55,27 +70,42 @@ def analyze_drift(X: np.ndarray, y: np.ndarray, drift_points: np.ndarray, drift_
|
|
| 55 |
def format_analysis_summary(analysis: Dict) -> str:
|
| 56 |
"""λΆμ κ²°κ³Όλ₯Ό μ¬λμ΄ μ½κΈ° μ¬μ΄ νμμΌλ‘ ν¬λ§·"""
|
| 57 |
|
|
|
|
|
|
|
| 58 |
summary = f"""
|
| 59 |
## λ리ννΈ λΆμ κ²°κ³Ό
|
| 60 |
|
| 61 |
-
**λ리ννΈ μ ν:** {
|
| 62 |
|
| 63 |
**μ 체 λ°μ΄ν°:**
|
| 64 |
- μ΄ μν μ: {analysis['total_samples']}
|
| 65 |
- λ리ννΈ λ°μ νμ: {analysis['num_drift_points']}
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
| 67 |
- νμ€νΈμ°¨: {analysis['std_y']:.2f}
|
| 68 |
- λ²μ: [{analysis['min_y']:.2f}, {analysis['max_y']:.2f}]
|
| 69 |
-
|
| 70 |
-
|
|
|
|
|
|
|
| 71 |
"""
|
| 72 |
|
|
|
|
|
|
|
| 73 |
for seg in analysis['segments']:
|
| 74 |
-
|
|
|
|
| 75 |
**μΈκ·Έλ¨ΌνΈ {seg['segment_id'] + 1}** (μν {seg['start_idx']}-{seg['end_idx']})
|
| 76 |
- νκ· : {seg['mean']:.2f}
|
| 77 |
- νμ€νΈμ°¨: {seg['std']:.2f}
|
| 78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
"""
|
| 80 |
|
| 81 |
return summary
|
|
|
|
| 11 |
"drift_locations": drift_points.tolist() if len(drift_points) > 0 else [],
|
| 12 |
}
|
| 13 |
|
| 14 |
+
# incremental driftλ μ°μ κ°, λλ¨Έμ§λ μ΄μ§ λΆλ₯
|
| 15 |
+
if drift_type == "incremental":
|
| 16 |
+
# μ°μ κ° λΆμ
|
| 17 |
+
analysis["mean_y"] = float(np.mean(y))
|
| 18 |
+
analysis["std_y"] = float(np.std(y))
|
| 19 |
+
analysis["min_y"] = float(np.min(y))
|
| 20 |
+
analysis["max_y"] = float(np.max(y))
|
| 21 |
+
else:
|
| 22 |
+
# μ΄μ§ λΆλ₯ λΆμ
|
| 23 |
+
class_0_count = int(np.sum(y == 0))
|
| 24 |
+
class_1_count = int(np.sum(y == 1))
|
| 25 |
+
analysis["class_0_count"] = class_0_count
|
| 26 |
+
analysis["class_1_count"] = class_1_count
|
| 27 |
+
analysis["class_0_ratio"] = float(class_0_count / len(y))
|
| 28 |
+
analysis["class_1_ratio"] = float(class_1_count / len(y))
|
| 29 |
|
| 30 |
# μΈκ·Έλ¨ΌνΈλ³ λΆμ
|
| 31 |
segments = []
|
|
|
|
| 36 |
end = segment_boundaries[i + 1]
|
| 37 |
|
| 38 |
segment_y = y[start:end]
|
|
|
|
| 39 |
|
| 40 |
+
if drift_type == "incremental":
|
| 41 |
+
# μ°μ κ° μΈκ·Έλ¨ΌνΈ λΆμ
|
| 42 |
+
segments.append({
|
| 43 |
+
"segment_id": i,
|
| 44 |
+
"start_idx": int(start),
|
| 45 |
+
"end_idx": int(end),
|
| 46 |
+
"mean": float(np.mean(segment_y)),
|
| 47 |
+
"std": float(np.std(segment_y))
|
| 48 |
+
})
|
| 49 |
else:
|
| 50 |
+
# μ΄μ§ λΆλ₯ μΈκ·Έλ¨ΌνΈ λΆμ
|
| 51 |
+
class_0_count = int(np.sum(segment_y == 0))
|
| 52 |
+
class_1_count = int(np.sum(segment_y == 1))
|
| 53 |
+
total = len(segment_y)
|
| 54 |
+
|
| 55 |
+
segments.append({
|
| 56 |
+
"segment_id": i,
|
| 57 |
+
"start_idx": int(start),
|
| 58 |
+
"end_idx": int(end),
|
| 59 |
+
"class_0_count": class_0_count,
|
| 60 |
+
"class_1_count": class_1_count,
|
| 61 |
+
"class_0_ratio": float(class_0_count / total) if total > 0 else 0.0,
|
| 62 |
+
"class_1_ratio": float(class_1_count / total) if total > 0 else 0.0
|
| 63 |
+
})
|
| 64 |
|
| 65 |
analysis["segments"] = segments
|
| 66 |
|
|
|
|
| 70 |
def format_analysis_summary(analysis: Dict) -> str:
|
| 71 |
"""λΆμ κ²°κ³Όλ₯Ό μ¬λμ΄ μ½κΈ° μ¬μ΄ νμμΌλ‘ ν¬λ§·"""
|
| 72 |
|
| 73 |
+
drift_type = analysis['drift_type']
|
| 74 |
+
|
| 75 |
summary = f"""
|
| 76 |
## λ리ννΈ λΆμ κ²°κ³Ό
|
| 77 |
|
| 78 |
+
**λ리ννΈ μ ν:** {drift_type.upper()}
|
| 79 |
|
| 80 |
**μ 체 λ°μ΄ν°:**
|
| 81 |
- μ΄ μν μ: {analysis['total_samples']}
|
| 82 |
- λ리ννΈ λ°μ νμ: {analysis['num_drift_points']}
|
| 83 |
+
"""
|
| 84 |
+
|
| 85 |
+
if drift_type == "incremental":
|
| 86 |
+
summary += f"""- νκ· κ°: {analysis['mean_y']:.2f}
|
| 87 |
- νμ€νΈμ°¨: {analysis['std_y']:.2f}
|
| 88 |
- λ²μ: [{analysis['min_y']:.2f}, {analysis['max_y']:.2f}]
|
| 89 |
+
"""
|
| 90 |
+
else:
|
| 91 |
+
summary += f"""- Class 0 (νλμ): {analysis['class_0_count']} μν ({analysis['class_0_ratio']*100:.1f}%)
|
| 92 |
+
- Class 1 (μ΄λ‘μ): {analysis['class_1_count']} μν ({analysis['class_1_ratio']*100:.1f}%)
|
| 93 |
"""
|
| 94 |
|
| 95 |
+
summary += "\n**μΈκ·Έλ¨ΌνΈλ³ λΆμ:**\n"
|
| 96 |
+
|
| 97 |
for seg in analysis['segments']:
|
| 98 |
+
if drift_type == "incremental":
|
| 99 |
+
summary += f"""
|
| 100 |
**μΈκ·Έλ¨ΌνΈ {seg['segment_id'] + 1}** (μν {seg['start_idx']}-{seg['end_idx']})
|
| 101 |
- νκ· : {seg['mean']:.2f}
|
| 102 |
- νμ€νΈμ°¨: {seg['std']:.2f}
|
| 103 |
+
"""
|
| 104 |
+
else:
|
| 105 |
+
summary += f"""
|
| 106 |
+
**μΈκ·Έλ¨ΌνΈ {seg['segment_id'] + 1}** (μν {seg['start_idx']}-{seg['end_idx']})
|
| 107 |
+
- Class 0: {seg['class_0_count']} μν ({seg['class_0_ratio']*100:.1f}%)
|
| 108 |
+
- Class 1: {seg['class_1_count']} μν ({seg['class_1_ratio']*100:.1f}%)
|
| 109 |
"""
|
| 110 |
|
| 111 |
return summary
|
drift_simulator.py
CHANGED
|
@@ -3,14 +3,14 @@ from typing import Tuple
|
|
| 3 |
|
| 4 |
def generate_sudden_drift(n_samples: int = 1000, drift_point: int = 500) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
| 5 |
"""κΈκ²©ν λ리ννΈ: t μμ μμ κ°μκΈ° λ°μ΄ν° λΆν¬ λ³κ²½"""
|
| 6 |
-
X = np.
|
| 7 |
-
y = np.zeros(n_samples)
|
| 8 |
|
| 9 |
-
# Before drift:
|
| 10 |
-
y[:drift_point] =
|
| 11 |
|
| 12 |
-
# After drift:
|
| 13 |
-
y[drift_point:] =
|
| 14 |
|
| 15 |
drift_points = np.array([drift_point])
|
| 16 |
return X, y, drift_points
|
|
@@ -18,44 +18,41 @@ def generate_sudden_drift(n_samples: int = 1000, drift_point: int = 500) -> Tupl
|
|
| 18 |
|
| 19 |
def generate_gradual_drift(n_samples: int = 1000, drift_start: int = 300, drift_end: int = 700) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
| 20 |
"""μ μ§μ λ리ννΈ: λ λΆν¬κ° μμ΄λ©° μ²μ²ν μ ν"""
|
| 21 |
-
X = np.
|
| 22 |
-
y = np.zeros(n_samples)
|
| 23 |
|
| 24 |
-
# Before drift:
|
| 25 |
-
y[:drift_start] =
|
| 26 |
|
| 27 |
-
# Gradual transition:
|
| 28 |
transition_length = drift_end - drift_start
|
| 29 |
for i in range(drift_start, drift_end):
|
|
|
|
| 30 |
weight = (i - drift_start) / transition_length
|
| 31 |
-
|
| 32 |
-
new_concept = -X[i] + 5 + np.random.normal(0, 1)
|
| 33 |
-
y[i] = (1 - weight) * old_concept + weight * new_concept
|
| 34 |
|
| 35 |
-
# After drift:
|
| 36 |
-
y[drift_end:] =
|
| 37 |
|
| 38 |
drift_points = np.array([drift_start, drift_end])
|
| 39 |
return X, y, drift_points
|
| 40 |
|
| 41 |
|
| 42 |
-
def generate_incremental_drift(n_samples: int = 1000, n_steps: int =
|
| 43 |
"""μ¦λΆμ λ리ννΈ: κ³λ¨μμΌλ‘ μμ λ³νκ° λμ """
|
| 44 |
-
X = np.
|
| 45 |
-
y = np.zeros(n_samples)
|
| 46 |
|
| 47 |
-
step_size = n_samples //
|
| 48 |
drift_points = []
|
| 49 |
|
| 50 |
-
for step in range(n_steps
|
| 51 |
start_idx = step * step_size
|
| 52 |
-
end_idx = (step + 1) * step_size if step < n_steps else n_samples
|
| 53 |
|
| 54 |
-
# κ° λ¨κ³λ§λ€
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
y[start_idx:end_idx] = slope * X[start_idx:end_idx] + intercept + np.random.normal(0, 1, end_idx - start_idx)
|
| 59 |
|
| 60 |
if step > 0:
|
| 61 |
drift_points.append(start_idx)
|
|
@@ -65,8 +62,8 @@ def generate_incremental_drift(n_samples: int = 1000, n_steps: int = 5) -> Tuple
|
|
| 65 |
|
| 66 |
def generate_recurring_drift(n_samples: int = 1000, cycle_length: int = 250) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
| 67 |
"""λ°λ³΅μ λ리ννΈ: μ΄μ λΆν¬κ° μ£ΌκΈ°μ μΌλ‘ μ¬λ±μ₯"""
|
| 68 |
-
X = np.
|
| 69 |
-
y = np.zeros(n_samples)
|
| 70 |
|
| 71 |
drift_points = []
|
| 72 |
|
|
@@ -74,11 +71,11 @@ def generate_recurring_drift(n_samples: int = 1000, cycle_length: int = 250) ->
|
|
| 74 |
cycle_pos = i % cycle_length
|
| 75 |
|
| 76 |
if cycle_pos < cycle_length // 2:
|
| 77 |
-
# Concept A:
|
| 78 |
-
y[i] =
|
| 79 |
else:
|
| 80 |
-
# Concept B:
|
| 81 |
-
y[i] =
|
| 82 |
|
| 83 |
if cycle_pos == cycle_length // 2:
|
| 84 |
drift_points.append(i)
|
|
|
|
| 3 |
|
| 4 |
def generate_sudden_drift(n_samples: int = 1000, drift_point: int = 500) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
| 5 |
"""κΈκ²©ν λ리ννΈ: t μμ μμ κ°μκΈ° λ°μ΄ν° λΆν¬ λ³κ²½"""
|
| 6 |
+
X = np.arange(n_samples) # μκ° μΈλ±μ€
|
| 7 |
+
y = np.zeros(n_samples, dtype=int)
|
| 8 |
|
| 9 |
+
# Before drift: class 0 (νλμ)
|
| 10 |
+
y[:drift_point] = 0
|
| 11 |
|
| 12 |
+
# After drift: class 1 (μ΄λ‘μ)
|
| 13 |
+
y[drift_point:] = 1
|
| 14 |
|
| 15 |
drift_points = np.array([drift_point])
|
| 16 |
return X, y, drift_points
|
|
|
|
| 18 |
|
| 19 |
def generate_gradual_drift(n_samples: int = 1000, drift_start: int = 300, drift_end: int = 700) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
| 20 |
"""μ μ§μ λ리ννΈ: λ λΆν¬κ° μμ΄λ©° μ²μ²ν μ ν"""
|
| 21 |
+
X = np.arange(n_samples) # μκ° μΈλ±μ€
|
| 22 |
+
y = np.zeros(n_samples, dtype=int)
|
| 23 |
|
| 24 |
+
# Before drift: class 0 (νλμ)
|
| 25 |
+
y[:drift_start] = 0
|
| 26 |
|
| 27 |
+
# Gradual transition: class 0κ³Ό class 1μ΄ μμ
|
| 28 |
transition_length = drift_end - drift_start
|
| 29 |
for i in range(drift_start, drift_end):
|
| 30 |
+
# μ μ§μ μΌλ‘ class 1μ λΉμ¨ μ¦κ°
|
| 31 |
weight = (i - drift_start) / transition_length
|
| 32 |
+
y[i] = 1 if np.random.random() < weight else 0
|
|
|
|
|
|
|
| 33 |
|
| 34 |
+
# After drift: class 1 (μ΄λ‘μ)
|
| 35 |
+
y[drift_end:] = 1
|
| 36 |
|
| 37 |
drift_points = np.array([drift_start, drift_end])
|
| 38 |
return X, y, drift_points
|
| 39 |
|
| 40 |
|
| 41 |
+
def generate_incremental_drift(n_samples: int = 1000, n_steps: int = 10) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
| 42 |
"""μ¦λΆμ λ리ννΈ: κ³λ¨μμΌλ‘ μμ λ³νκ° λμ """
|
| 43 |
+
X = np.arange(n_samples) # μκ° μΈλ±μ€
|
| 44 |
+
y = np.zeros(n_samples) # μ°μ κ° (μκ°νλ₯Ό μν΄)
|
| 45 |
|
| 46 |
+
step_size = n_samples // n_steps
|
| 47 |
drift_points = []
|
| 48 |
|
| 49 |
+
for step in range(n_steps):
|
| 50 |
start_idx = step * step_size
|
| 51 |
+
end_idx = (step + 1) * step_size if step < n_steps - 1 else n_samples
|
| 52 |
|
| 53 |
+
# κ° λ¨κ³λ§λ€ 0μμ 1λ‘ μ μ§μ λ³ν
|
| 54 |
+
value = step / (n_steps - 1)
|
| 55 |
+
y[start_idx:end_idx] = value
|
|
|
|
|
|
|
| 56 |
|
| 57 |
if step > 0:
|
| 58 |
drift_points.append(start_idx)
|
|
|
|
| 62 |
|
| 63 |
def generate_recurring_drift(n_samples: int = 1000, cycle_length: int = 250) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
| 64 |
"""λ°λ³΅μ λ리ννΈ: μ΄μ λΆν¬κ° μ£ΌκΈ°μ μΌλ‘ μ¬λ±μ₯"""
|
| 65 |
+
X = np.arange(n_samples) # μκ° μΈλ±μ€
|
| 66 |
+
y = np.zeros(n_samples, dtype=int)
|
| 67 |
|
| 68 |
drift_points = []
|
| 69 |
|
|
|
|
| 71 |
cycle_pos = i % cycle_length
|
| 72 |
|
| 73 |
if cycle_pos < cycle_length // 2:
|
| 74 |
+
# Concept A: class 0 (νλμ)
|
| 75 |
+
y[i] = 0
|
| 76 |
else:
|
| 77 |
+
# Concept B: class 1 (μ΄λ‘μ)
|
| 78 |
+
y[i] = 1
|
| 79 |
|
| 80 |
if cycle_pos == cycle_length // 2:
|
| 81 |
drift_points.append(i)
|
visualizer.py
CHANGED
|
@@ -7,42 +7,60 @@ def create_drift_visualization(X: np.ndarray, y: np.ndarray, drift_points: np.nd
|
|
| 7 |
|
| 8 |
fig = go.Figure()
|
| 9 |
|
| 10 |
-
#
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
# λ μ΄μμ μ€μ
|
| 41 |
title_map = {
|
| 42 |
-
"sudden": "Sudden
|
| 43 |
-
"gradual": "Gradual Drift
|
| 44 |
-
"incremental": "Incremental Drift
|
| 45 |
-
"recurring": "
|
| 46 |
}
|
| 47 |
|
| 48 |
fig.update_layout(
|
|
@@ -50,31 +68,31 @@ def create_drift_visualization(X: np.ndarray, y: np.ndarray, drift_points: np.nd
|
|
| 50 |
text=title_map.get(drift_type, "Concept Drift"),
|
| 51 |
x=0.5,
|
| 52 |
xanchor='center',
|
| 53 |
-
font=dict(size=20)
|
| 54 |
),
|
| 55 |
-
xaxis_title="
|
| 56 |
-
yaxis_title="
|
| 57 |
hovermode='closest',
|
| 58 |
template='plotly_white',
|
| 59 |
-
height=
|
| 60 |
-
showlegend=
|
| 61 |
legend=dict(
|
| 62 |
yanchor="top",
|
| 63 |
y=0.99,
|
| 64 |
-
xanchor="
|
| 65 |
-
x=0.
|
| 66 |
),
|
| 67 |
xaxis=dict(
|
| 68 |
-
showgrid=
|
| 69 |
-
|
| 70 |
-
gridcolor='LightGray'
|
| 71 |
),
|
| 72 |
yaxis=dict(
|
| 73 |
-
showgrid=
|
| 74 |
-
|
| 75 |
-
|
| 76 |
),
|
| 77 |
-
plot_bgcolor='white'
|
|
|
|
| 78 |
)
|
| 79 |
|
| 80 |
return fig
|
|
@@ -86,7 +104,9 @@ def create_comparison_visualization(drift_data_dict: dict) -> go.Figure:
|
|
| 86 |
|
| 87 |
fig = make_subplots(
|
| 88 |
rows=2, cols=2,
|
| 89 |
-
subplot_titles=("Sudden Drift", "Gradual Drift", "Incremental Drift", "
|
|
|
|
|
|
|
| 90 |
)
|
| 91 |
|
| 92 |
positions = [(1, 1), (1, 2), (2, 1), (2, 2)]
|
|
@@ -96,28 +116,59 @@ def create_comparison_visualization(drift_data_dict: dict) -> go.Figure:
|
|
| 96 |
if drift_type in drift_data_dict:
|
| 97 |
X, y, drift_points = drift_data_dict[drift_type]
|
| 98 |
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
line_dash="dash",
|
| 115 |
-
line_color="red",
|
| 116 |
row=row, col=col
|
| 117 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
|
| 119 |
-
|
| 120 |
-
fig.
|
| 121 |
-
fig.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
|
| 123 |
return fig
|
|
|
|
| 7 |
|
| 8 |
fig = go.Figure()
|
| 9 |
|
| 10 |
+
# incremental driftλ μ°μ κ°μΌλ‘ μ²λ¦¬
|
| 11 |
+
if drift_type == "incremental":
|
| 12 |
+
# 0-1 μ¬μ΄ κ°μ μμμΌλ‘ λ§€ν
|
| 13 |
+
colors = []
|
| 14 |
+
for val in y:
|
| 15 |
+
# νλμ(0)μμ μ΄λ‘μ(1)λ‘ μ μ§μ λ³ν
|
| 16 |
+
blue = int(255 * (1 - val))
|
| 17 |
+
green = int(255 * val)
|
| 18 |
+
colors.append(f'rgb({blue}, {green}, 150)')
|
| 19 |
+
|
| 20 |
+
fig.add_trace(go.Bar(
|
| 21 |
+
x=X,
|
| 22 |
+
y=np.ones(len(X)),
|
| 23 |
+
marker=dict(
|
| 24 |
+
color=colors,
|
| 25 |
+
line=dict(width=0)
|
| 26 |
+
),
|
| 27 |
+
showlegend=False,
|
| 28 |
+
hovertemplate='Time: %{x}<br>Value: %{customdata:.2f}<extra></extra>',
|
| 29 |
+
customdata=y
|
| 30 |
+
))
|
| 31 |
+
else:
|
| 32 |
+
# μ΄μ§ λΆλ₯ (0: νλμ, 1: μ΄λ‘μ)
|
| 33 |
+
class_0_indices = np.where(y == 0)[0]
|
| 34 |
+
class_1_indices = np.where(y == 1)[0]
|
| 35 |
+
|
| 36 |
+
# Class 0 (νλμ)
|
| 37 |
+
if len(class_0_indices) > 0:
|
| 38 |
+
fig.add_trace(go.Bar(
|
| 39 |
+
x=X[class_0_indices],
|
| 40 |
+
y=np.ones(len(class_0_indices)),
|
| 41 |
+
marker=dict(color='rgb(70, 130, 180)', line=dict(width=0)),
|
| 42 |
+
name='Class 0',
|
| 43 |
+
showlegend=True,
|
| 44 |
+
hovertemplate='Time: %{x}<br>Class: 0<extra></extra>'
|
| 45 |
+
))
|
| 46 |
+
|
| 47 |
+
# Class 1 (μ΄λ‘μ)
|
| 48 |
+
if len(class_1_indices) > 0:
|
| 49 |
+
fig.add_trace(go.Bar(
|
| 50 |
+
x=X[class_1_indices],
|
| 51 |
+
y=np.ones(len(class_1_indices)),
|
| 52 |
+
marker=dict(color='rgb(60, 179, 113)', line=dict(width=0)),
|
| 53 |
+
name='Class 1',
|
| 54 |
+
showlegend=True,
|
| 55 |
+
hovertemplate='Time: %{x}<br>Class: 1<extra></extra>'
|
| 56 |
+
))
|
| 57 |
|
| 58 |
# λ μ΄μμ μ€μ
|
| 59 |
title_map = {
|
| 60 |
+
"sudden": "Sudden Drift",
|
| 61 |
+
"gradual": "Gradual Drift",
|
| 62 |
+
"incremental": "Incremental Drift",
|
| 63 |
+
"recurring": "Reoccurring Concepts"
|
| 64 |
}
|
| 65 |
|
| 66 |
fig.update_layout(
|
|
|
|
| 68 |
text=title_map.get(drift_type, "Concept Drift"),
|
| 69 |
x=0.5,
|
| 70 |
xanchor='center',
|
| 71 |
+
font=dict(size=20, weight='bold')
|
| 72 |
),
|
| 73 |
+
xaxis_title="Time",
|
| 74 |
+
yaxis_title="Data distribution",
|
| 75 |
hovermode='closest',
|
| 76 |
template='plotly_white',
|
| 77 |
+
height=400,
|
| 78 |
+
showlegend=(drift_type != "incremental"),
|
| 79 |
legend=dict(
|
| 80 |
yanchor="top",
|
| 81 |
y=0.99,
|
| 82 |
+
xanchor="right",
|
| 83 |
+
x=0.99
|
| 84 |
),
|
| 85 |
xaxis=dict(
|
| 86 |
+
showgrid=False,
|
| 87 |
+
showticklabels=False
|
|
|
|
| 88 |
),
|
| 89 |
yaxis=dict(
|
| 90 |
+
showgrid=False,
|
| 91 |
+
showticklabels=False,
|
| 92 |
+
range=[0, 1.2]
|
| 93 |
),
|
| 94 |
+
plot_bgcolor='white',
|
| 95 |
+
bargap=0
|
| 96 |
)
|
| 97 |
|
| 98 |
return fig
|
|
|
|
| 104 |
|
| 105 |
fig = make_subplots(
|
| 106 |
rows=2, cols=2,
|
| 107 |
+
subplot_titles=("Sudden Drift", "Gradual Drift", "Incremental Drift", "Reoccurring Concepts"),
|
| 108 |
+
vertical_spacing=0.15,
|
| 109 |
+
horizontal_spacing=0.1
|
| 110 |
)
|
| 111 |
|
| 112 |
positions = [(1, 1), (1, 2), (2, 1), (2, 2)]
|
|
|
|
| 116 |
if drift_type in drift_data_dict:
|
| 117 |
X, y, drift_points = drift_data_dict[drift_type]
|
| 118 |
|
| 119 |
+
if drift_type == "incremental":
|
| 120 |
+
# Incremental drift: μ°μ μμ λ³ν
|
| 121 |
+
colors = []
|
| 122 |
+
for val in y:
|
| 123 |
+
blue = int(255 * (1 - val))
|
| 124 |
+
green = int(255 * val)
|
| 125 |
+
colors.append(f'rgb({blue}, {green}, 150)')
|
| 126 |
+
|
| 127 |
+
fig.add_trace(
|
| 128 |
+
go.Bar(
|
| 129 |
+
x=X,
|
| 130 |
+
y=np.ones(len(X)),
|
| 131 |
+
marker=dict(color=colors, line=dict(width=0)),
|
| 132 |
+
showlegend=False
|
| 133 |
+
),
|
|
|
|
|
|
|
| 134 |
row=row, col=col
|
| 135 |
)
|
| 136 |
+
else:
|
| 137 |
+
# μ΄μ§ λΆλ₯
|
| 138 |
+
class_0_indices = np.where(y == 0)[0]
|
| 139 |
+
class_1_indices = np.where(y == 1)[0]
|
| 140 |
+
|
| 141 |
+
if len(class_0_indices) > 0:
|
| 142 |
+
fig.add_trace(
|
| 143 |
+
go.Bar(
|
| 144 |
+
x=X[class_0_indices],
|
| 145 |
+
y=np.ones(len(class_0_indices)),
|
| 146 |
+
marker=dict(color='rgb(70, 130, 180)', line=dict(width=0)),
|
| 147 |
+
showlegend=False
|
| 148 |
+
),
|
| 149 |
+
row=row, col=col
|
| 150 |
+
)
|
| 151 |
+
|
| 152 |
+
if len(class_1_indices) > 0:
|
| 153 |
+
fig.add_trace(
|
| 154 |
+
go.Bar(
|
| 155 |
+
x=X[class_1_indices],
|
| 156 |
+
y=np.ones(len(class_1_indices)),
|
| 157 |
+
marker=dict(color='rgb(60, 179, 113)', line=dict(width=0)),
|
| 158 |
+
showlegend=False
|
| 159 |
+
),
|
| 160 |
+
row=row, col=col
|
| 161 |
+
)
|
| 162 |
|
| 163 |
+
# λ μ΄μμ μ€μ
|
| 164 |
+
fig.update_xaxes(title_text="Time", showgrid=False, showticklabels=False)
|
| 165 |
+
fig.update_yaxes(title_text="Data distribution", showgrid=False, showticklabels=False, range=[0, 1.2])
|
| 166 |
+
fig.update_layout(
|
| 167 |
+
height=800,
|
| 168 |
+
title_text="Concept Drift Types Comparison",
|
| 169 |
+
showlegend=False,
|
| 170 |
+
bargap=0,
|
| 171 |
+
template='plotly_white'
|
| 172 |
+
)
|
| 173 |
|
| 174 |
return fig
|