|
1 | 1 | import matplotlib.pyplot as plt |
2 | 2 | import numpy as np |
3 | 3 | import os |
| 4 | +import pandas as pd |
| 5 | +from matplotlib import gridspec |
4 | 6 |
|
5 | 7 |
|
6 | 8 | def simple_contour( |
@@ -513,3 +515,207 @@ def plotCombinations( |
513 | 515 | ) |
514 | 516 |
|
515 | 517 | return None |
| 518 | + |
| 519 | + |
| 520 | +def create_contour_plot(data, x_col, y_col, z_col, facet_col=None, aspect=1, as_table=True, figsize=(3, 3), levels=5, cmap="viridis"): |
| 521 | + """ |
| 522 | + Creates contour plots similar to R's contourplot function using matplotlib. |
| 523 | +
|
| 524 | + Args: |
| 525 | + data (pd.DataFrame): The DataFrame containing the data. |
| 526 | + x_col (str): The name of the column to use for the x-axis. |
| 527 | + y_col (str): The name of the column to use for the y-axis. |
| 528 | + z_col (str): The name of the column to use for the z-axis (contour values). |
| 529 | + facet_col (str, optional): The name of the column to use for faceting (creating subplots). Defaults to None. |
| 530 | + aspect (float, optional): The aspect ratio of the plot. Defaults to 1. |
| 531 | + as_table (bool, optional): Whether to arrange facets as a table. Defaults to True. |
| 532 | + figsize (tuple, optional): The size of the figure. Defaults to (3, 3). |
| 533 | + levels (int, optional): The number of contour levels. Defaults to 5. |
| 534 | + cmap (str, optional): The colormap to use. Defaults to "viridis". |
| 535 | +
|
| 536 | + Returns: |
| 537 | + None: Displays the contour plot(s). |
| 538 | +
|
| 539 | + Raises: |
| 540 | + ValueError: If the specified columns are not found in the DataFrame. |
| 541 | +
|
| 542 | + Examples: |
| 543 | + >>> from spotpython.plot.contour import create_contour_plot |
| 544 | + import numpy as np |
| 545 | + import pandas as pd |
| 546 | + # Create a grid of x and y values |
| 547 | + x = np.linspace(-5, 5, 100) |
| 548 | + y = np.linspace(-5, 5, 100) |
| 549 | + x_grid, y_grid = np.meshgrid(x, y) |
| 550 | + # Calculate z = x^2 + y^2 |
| 551 | + z = x_grid**2 + y_grid**2 |
| 552 | + # Flatten the grid and create a DataFrame |
| 553 | + data = pd.DataFrame({ |
| 554 | + 'x': x_grid.flatten(), |
| 555 | + 'y': y_grid.flatten(), |
| 556 | + 'z': z.flatten() |
| 557 | + }) |
| 558 | + # Create the contour plot |
| 559 | + create_contour_plot(data, 'x', 'y', 'z', facet_col=None) |
| 560 | + >>> # Create a contour plot with faceting |
| 561 | + from spotpython.plot.contour import create_contour_plot |
| 562 | + import numpy as np |
| 563 | + import pandas as pd |
| 564 | + # Create a grid of x and y values |
| 565 | + x = np.linspace(-5, 5, 50) |
| 566 | + y = np.linspace(-5, 5, 50) |
| 567 | + x_grid, y_grid = np.meshgrid(x, y) |
| 568 | + # Calculate z = x^2 + y^2 for two different facets |
| 569 | + z1 = x_grid**2 + y_grid**2 |
| 570 | + z2 = (x_grid - 2)**2 + (y_grid - 2)**2 |
| 571 | + # Flatten the grids and create a DataFrame |
| 572 | + data = pd.DataFrame({ |
| 573 | + 'x': np.tile(x, len(y) * 2), # Repeat x values for both facets |
| 574 | + 'y': np.repeat(y, len(x) * 2), # Repeat y values for both facets |
| 575 | + 'z': np.concatenate([z1.flatten(), z2.flatten()]), # Combine z values for both facets |
| 576 | + 'facet': ['Facet A'] * len(z1.flatten()) + ['Facet B'] * len(z2.flatten()) # Create facet column |
| 577 | + }) |
| 578 | + # Create the contour plot with facets |
| 579 | + create_contour_plot(data, 'x', 'y', 'z', facet_col='facet') |
| 580 | + """ |
| 581 | + |
| 582 | + if facet_col: |
| 583 | + facet_values = data[facet_col].unique() |
| 584 | + num_facets = len(facet_values) |
| 585 | + |
| 586 | + # Determine subplot layout |
| 587 | + if as_table: |
| 588 | + num_cols = int(np.ceil(np.sqrt(num_facets))) |
| 589 | + num_rows = int(np.ceil(num_facets / num_cols)) |
| 590 | + else: |
| 591 | + num_cols = num_facets |
| 592 | + num_rows = 1 |
| 593 | + |
| 594 | + fig, axes = plt.subplots(num_rows, num_cols, figsize=(figsize[0] * num_cols, figsize[1] * num_rows)) |
| 595 | + axes = np.array(axes).flatten() # Flatten the axes array for easy indexing |
| 596 | + |
| 597 | + for i, facet_value in enumerate(facet_values): |
| 598 | + ax = axes[i] |
| 599 | + facet_data = data[data[facet_col] == facet_value] |
| 600 | + |
| 601 | + # Create grid for contour plot |
| 602 | + x = np.unique(facet_data[x_col]) |
| 603 | + y = np.unique(facet_data[y_col]) |
| 604 | + X, Y = np.meshgrid(x, y) |
| 605 | + Z = facet_data.pivot_table(index=y_col, columns=x_col, values=z_col).values |
| 606 | + |
| 607 | + # Plot contour |
| 608 | + contour = ax.contour(X, Y, Z, levels=levels, cmap=cmap) # Adjust levels and cmap as needed |
| 609 | + ax.clabel(contour, inline=True, fontsize=8) |
| 610 | + |
| 611 | + # Set labels and title |
| 612 | + ax.set_xlabel(x_col) |
| 613 | + ax.set_ylabel(y_col) |
| 614 | + ax.set_title(f"{facet_col} = {np.round(facet_value,2)}") |
| 615 | + ax.set_aspect(aspect) |
| 616 | + |
| 617 | + # Remove empty subplots |
| 618 | + for i in range(num_facets, len(axes)): |
| 619 | + fig.delaxes(axes[i]) |
| 620 | + |
| 621 | + fig.tight_layout() |
| 622 | + plt.show() |
| 623 | + |
| 624 | + else: |
| 625 | + # Create grid for contour plot |
| 626 | + x = np.unique(data[x_col]) |
| 627 | + y = np.unique(data[y_col]) |
| 628 | + X, Y = np.meshgrid(x, y) |
| 629 | + Z = data.pivot_table(index=y_col, columns=x_col, values=z_col).values |
| 630 | + |
| 631 | + # Plot contour |
| 632 | + fig, ax = plt.subplots(figsize=figsize) |
| 633 | + contour = ax.contour(X, Y, Z, levels=10, cmap="viridis") # Adjust levels and cmap as needed |
| 634 | + ax.clabel(contour, inline=True, fontsize=8) |
| 635 | + |
| 636 | + # Set labels and title |
| 637 | + ax.set_xlabel(x_col) |
| 638 | + ax.set_ylabel(y_col) |
| 639 | + ax.set_title(f"Contour Plot of {z_col}") |
| 640 | + ax.set_aspect(aspect) |
| 641 | + |
| 642 | + plt.show() |
| 643 | + |
| 644 | + |
| 645 | +def mo_generate_plot_grid(variables, resolutions, functions): |
| 646 | + """ |
| 647 | + Generate a grid of input variables and apply objective functions. |
| 648 | +
|
| 649 | + Args: |
| 650 | + variables (dict): A dictionary where keys are variable names (e.g., "time", "temperature") |
| 651 | + and values are tuples of (min_value, max_value). |
| 652 | + resolutions (dict): A dictionary where keys are variable names and values are the number of points. |
| 653 | + functions (dict): A dictionary where keys are function names and values are callable functions. |
| 654 | +
|
| 655 | + Returns: |
| 656 | + pd.DataFrame: A DataFrame containing the grid and the results of the objective functions. |
| 657 | + """ |
| 658 | + # Create a meshgrid for all variables |
| 659 | + grids = [np.linspace(variables[var][0], variables[var][1], resolutions[var]) for var in variables] |
| 660 | + grid = np.array(np.meshgrid(*grids)).T.reshape(-1, len(variables)) |
| 661 | + |
| 662 | + # Create a DataFrame for the grid |
| 663 | + plot_grid = pd.DataFrame(grid, columns=variables.keys()) |
| 664 | + |
| 665 | + # Apply each function to the grid |
| 666 | + for func_name, func in functions.items(): |
| 667 | + plot_grid[func_name] = plot_grid.apply(lambda row: func(row.values), axis=1) |
| 668 | + |
| 669 | + return plot_grid |
| 670 | + |
| 671 | + |
| 672 | +def mo_create_contour_plots(plot_grid, x_col, y_col, z_col, facet_col, z_label="Objective", cmap="viridis", levels=10): |
| 673 | + """ |
| 674 | + Create contour plots for a given grid of data. |
| 675 | +
|
| 676 | + Args: |
| 677 | + plot_grid (pd.DataFrame): The data containing the grid and objective values. |
| 678 | + x_col (str): The column name for the x-axis. |
| 679 | + y_col (str): The column name for the y-axis. |
| 680 | + z_col (str): The column name for the z-axis (objective values). |
| 681 | + facet_col (str): The column name for the facet (e.g., temperature). |
| 682 | + z_label (str): Label for the colorbar. |
| 683 | + cmap (str): Colormap for the contour plot. |
| 684 | + levels (int): Number of contour levels. |
| 685 | + """ |
| 686 | + unique_facets = plot_grid[facet_col].unique() |
| 687 | + n_facets = len(unique_facets) |
| 688 | + |
| 689 | + # Set up a grid of subplots |
| 690 | + n_cols = 2 |
| 691 | + n_rows = (n_facets + 1) // n_cols |
| 692 | + fig = plt.figure(figsize=(12, 5 * n_rows)) |
| 693 | + gs = gridspec.GridSpec(n_rows, n_cols + 1, width_ratios=[1] * n_cols + [0.05]) # Add space for colorbar |
| 694 | + |
| 695 | + axes = [fig.add_subplot(gs[i // n_cols, i % n_cols]) for i in range(n_facets)] |
| 696 | + |
| 697 | + for i, facet in enumerate(unique_facets): |
| 698 | + # Filter data for the current facet |
| 699 | + facet_data = plot_grid[plot_grid[facet_col] == facet] |
| 700 | + |
| 701 | + # Pivot the data for contour plotting |
| 702 | + pivot_table = facet_data.pivot(index=y_col, columns=x_col, values=z_col) |
| 703 | + |
| 704 | + # Create the contour plot |
| 705 | + ax = axes[i] |
| 706 | + contour = ax.contourf(pivot_table.columns, pivot_table.index, pivot_table.values, cmap=cmap, levels=levels) # x-axis # y-axis # z-axis |
| 707 | + contour_lines = ax.contour(pivot_table.columns, pivot_table.index, pivot_table.values, colors="black", linewidths=0.5, levels=levels) |
| 708 | + ax.clabel(contour_lines, inline=True, fontsize=8) # Add labels to contour lines |
| 709 | + |
| 710 | + # Set plot labels and title |
| 711 | + ax.set_title(f"{facet_col} = {facet:.2f}") |
| 712 | + ax.set_xlabel(x_col) |
| 713 | + ax.set_ylabel(y_col) |
| 714 | + |
| 715 | + # Add a colorbar to the right of the plots |
| 716 | + cbar_ax = fig.add_subplot(gs[:, -1]) # Use the last column for the colorbar |
| 717 | + fig.colorbar(contour, cax=cbar_ax, orientation="vertical", label=z_label) |
| 718 | + |
| 719 | + # Adjust layout and show the plot |
| 720 | + plt.tight_layout() |
| 721 | + plt.show() |
0 commit comments