CODE = '''
import numpy as np

class LogisticRegression:
    """
    Logistic Regression implementation from scratch using Gradient Descent.
    
    Parameters:
    -----------
    learning_rate : float, default=0.01
        Learning rate for gradient descent
    n_iterations : int, default=1000
        Number of iterations for gradient descent
    """
    
    def __init__(self, learning_rate=0.01, n_iterations=1000):
        self.learning_rate = learning_rate
        self.n_iterations = n_iterations
        self.weights = None
        self.bias = None
        self.losses = []
    
    def _sigmoid(self, z):
        """Sigmoid activation function."""
        return 1 / (1 + np.exp(-z))
    
    def fit(self, X, y):
        """
        Fit the logistic regression model.
        
        Parameters:
        -----------
        X : array-like, shape (n_samples, n_features)
            Training data
        y : array-like, shape (n_samples,)
            Target values (0 or 1)
        """
        n_samples, n_features = X.shape
        
        # Initialize parameters
        self.weights = np.zeros(n_features)
        self.bias = 0
        
        # Gradient descent
        for i in range(self.n_iterations):
            # Linear combination
            linear_pred = np.dot(X, self.weights) + self.bias
            # Apply sigmoid
            predictions = self._sigmoid(linear_pred)
            
            # Calculate gradients
            dw = (1/n_samples) * np.dot(X.T, (predictions - y))
            db = (1/n_samples) * np.sum(predictions - y)
            
            # Update parameters
            self.weights -= self.learning_rate * dw
            self.bias -= self.learning_rate * db
            
            # Calculate and store loss (binary cross-entropy)
            epsilon = 1e-15
            predictions = np.clip(predictions, epsilon, 1 - epsilon)
            loss = -np.mean(y * np.log(predictions) + (1-y) * np.log(1-predictions))
            self.losses.append(loss)
    
    def predict_proba(self, X):
        """
        Predict probability estimates.
        
        Parameters:
        -----------
        X : array-like, shape (n_samples, n_features)
            Test data
            
        Returns:
        --------
        probabilities : array, shape (n_samples,)
            Probability estimates
        """
        linear_pred = np.dot(X, self.weights) + self.bias
        return self._sigmoid(linear_pred)
    
    def predict(self, X, threshold=0.5):
        """
        Make binary predictions.
        
        Parameters:
        -----------
        X : array-like, shape (n_samples, n_features)
            Test data
        threshold : float, default=0.5
            Classification threshold
            
        Returns:
        --------
        predictions : array, shape (n_samples,)
            Binary predictions (0 or 1)
        """
        return (self.predict_proba(X) >= threshold).astype(int)


# Example usage:
if __name__ == "__main__":
    from sklearn.datasets import make_classification
    from sklearn.model_selection import train_test_split
    
    # Generate sample data
    X, y = make_classification(n_samples=200, n_features=2, n_redundant=0, 
                               n_informative=2, random_state=42)
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
    
    # Create and train model
    model = LogisticRegression(learning_rate=0.1, n_iterations=1000)
    model.fit(X_train, y_train)
    
    # Make predictions
    predictions = model.predict(X_test)
    accuracy = np.mean(predictions == y_test)
    
    print(f"Accuracy: {accuracy:.4f}")
    print(f"Final Loss: {model.losses[-1]:.4f}")
'''