Files
IIabm/analysis_firm_risk_component.ipynb
T

761 KiB

1

In [1]:
import pandas as pd
count = pd.read_csv('analysis\\count.csv')
In [2]:
count.head()
Out [2]:
s_id id_firm id_product ts
0 2 126 1.4 1
1 2 0 1.4.4 0
2 4 0 1.4.4 0
3 4 126 1.4 1
4 5 126 1.4 1
In [3]:
count[count['id_firm'] == 126].shape
Out [3]:
(1955, 4)
In [4]:
# count row of each firm rename ts to count
count.groupby('id_firm').count()['ts'].rename('count').to_frame()
Out [4]:
count
id_firm
0 352
1 31
2 33
3 124
4 34
... ...
166 31
167 21
168 175
169 22
170 1525

171 rows × 1 columns

In [5]:
firm = pd.read_csv('Firm.csv')
firm.head()
Out [5]:
Code Name Stock_Region Stock_Name Stock_Code Chinese_Name Report_Year Assets Revenue Size ... 2.1.4.1.3 2.1.4.1.4 2.1.4.2 2.1.4.2.1 2.1.4.2.2 2.2 2.3 2.3.1 2.3.2 2.3.3
0 0 360科技 SH 三六零 601360.SH 三六零安全科技股份有限公司 2021.0 4.204000e+10 1.089000e+10 L ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1 1 51WORLD NaN 51WORLD NaN 北京五一视界数字孪生科技股份有限公司 2021.0 5.240000e+08 1.380000e+08 M ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
2 2 706所 NaN 706所 NaN 北京航天爱威电子技术有限公司 NaN NaN NaN M ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
3 3 艾克斯特 NaN 艾克斯特 NaN 北京艾克斯特科技有限公司 NaN NaN NaN S ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
4 4 爱创科技 NaN 爱创科技 NaN 北京爱创科技股份有限公司 NaN NaN NaN M ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

5 rows × 120 columns

In [6]:
count.merge(firm[['Code', 'Assets', 'Revenue', 'Size']], how='left', left_on='id_firm', right_on='Code')
Out [6]:
s_id id_firm id_product ts Code Assets Revenue Size
0 2 126 1.4 1 126 NaN 6.368070e+11 L
1 2 0 1.4.4 0 0 4.204000e+10 1.089000e+10 L
2 4 0 1.4.4 0 0 4.204000e+10 1.089000e+10 L
3 4 126 1.4 1 126 NaN 6.368070e+11 L
4 5 126 1.4 1 126 NaN 6.368070e+11 L
... ... ... ... ... ... ... ... ...
31916 23694 86 1.1 1 86 8.490952e+11 5.969638e+11 NaN
31917 23695 169 1.1.1 0 169 2.302330e+11 3.470400e+10 L
31918 23695 105 1.1 1 105 1.158633e+12 5.436851e+11 NaN
31919 23699 169 1.1.1 0 169 2.302330e+11 3.470400e+10 L
31920 23699 105 1.1 1 105 1.158633e+12 5.436851e+11 NaN

31921 rows × 8 columns

In [7]:
from orm import db_session, Sample, engine
import pandas as pd

# Read your count data
count = pd.read_csv('analysis\\count.csv')

# Query to get s_id and g_firm (firm network)
query = db_session.query(
    Sample.id.label('s_id'),
    Sample.e_id,
    Sample.g_firm
)

# Convert to dataframe using engine.connect()
with engine.connect() as conn:
    sample_df = pd.read_sql(query.statement, conn)

# Now merge with your count data
count_with_gfirm = count.merge(
    sample_df[['s_id', 'e_id', 'g_firm']], 
    how='left', 
    on='s_id'
)

count_with_gfirm.head()
DB is localhost:3306/iiabmdb
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
Cell In[7], line 16
     14 # Convert to dataframe using engine.connect()
     15 with engine.connect() as conn:
---> 16     sample_df = pd.read_sql(query.statement, conn)
     18 # Now merge with your count data
     19 count_with_gfirm = count.merge(
     20     sample_df[['s_id', 'e_id', 'g_firm']], 
     21     how='left', 
     22     on='s_id'
     23 )

File c:\Users\ASUS\Documents\Project\IIabm\venv\lib\site-packages\pandas\io\sql.py:592, in read_sql(sql, con, index_col, coerce_float, params, parse_dates, columns, chunksize)
    583     return pandas_sql.read_table(
    584         sql,
    585         index_col=index_col,
   (...)
    589         chunksize=chunksize,
    590     )
    591 else:
--> 592     return pandas_sql.read_query(
    593         sql,
    594         index_col=index_col,
    595         params=params,
    596         coerce_float=coerce_float,
    597         parse_dates=parse_dates,
    598         chunksize=chunksize,
    599     )

File c:\Users\ASUS\Documents\Project\IIabm\venv\lib\site-packages\pandas\io\sql.py:1557, in SQLDatabase.read_query(self, sql, index_col, coerce_float, parse_dates, params, chunksize, dtype)
   1509 """
   1510 Read SQL query into a DataFrame.
   1511 
   (...)
   1553 
   1554 """
   1555 args = _convert_params(sql, params)
-> 1557 result = self.execute(*args)
   1558 columns = result.keys()
   1560 if chunksize is not None:

File c:\Users\ASUS\Documents\Project\IIabm\venv\lib\site-packages\pandas\io\sql.py:1402, in SQLDatabase.execute(self, *args, **kwargs)
   1400 def execute(self, *args, **kwargs):
   1401     """Simple passthrough to SQLAlchemy connectable"""
-> 1402     return self.connectable.execution_options().execute(*args, **kwargs)

File c:\Users\ASUS\Documents\Project\IIabm\venv\lib\site-packages\sqlalchemy\engine\base.py:1419, in Connection.execute(self, statement, parameters, execution_options)
   1417     raise exc.ObjectNotExecutableError(statement) from err
   1418 else:
-> 1419     return meth(
   1420         self,
   1421         distilled_parameters,
   1422         execution_options or NO_OPTIONS,
   1423     )

File c:\Users\ASUS\Documents\Project\IIabm\venv\lib\site-packages\sqlalchemy\sql\elements.py:527, in ClauseElement._execute_on_connection(self, connection, distilled_params, execution_options)
    525     if TYPE_CHECKING:
    526         assert isinstance(self, Executable)
--> 527     return connection._execute_clauseelement(
    528         self, distilled_params, execution_options
    529     )
    530 else:
    531     raise exc.ObjectNotExecutableError(self)

File c:\Users\ASUS\Documents\Project\IIabm\venv\lib\site-packages\sqlalchemy\engine\base.py:1641, in Connection._execute_clauseelement(self, elem, distilled_parameters, execution_options)
   1629 compiled_cache: Optional[CompiledCacheType] = execution_options.get(
   1630     "compiled_cache", self.engine._compiled_cache
   1631 )
   1633 compiled_sql, extracted_params, cache_hit = elem._compile_w_cache(
   1634     dialect=dialect,
   1635     compiled_cache=compiled_cache,
   (...)
   1639     linting=self.dialect.compiler_linting | compiler.WARN_LINTING,
   1640 )
-> 1641 ret = self._execute_context(
   1642     dialect,
   1643     dialect.execution_ctx_cls._init_compiled,
   1644     compiled_sql,
   1645     distilled_parameters,
   1646     execution_options,
   1647     compiled_sql,
   1648     distilled_parameters,
   1649     elem,
   1650     extracted_params,
   1651     cache_hit=cache_hit,
   1652 )
   1653 if has_events:
   1654     self.dispatch.after_execute(
   1655         self,
   1656         elem,
   (...)
   1660         ret,
   1661     )

File c:\Users\ASUS\Documents\Project\IIabm\venv\lib\site-packages\sqlalchemy\engine\base.py:1846, in Connection._execute_context(self, dialect, constructor, statement, parameters, execution_options, *args, **kw)
   1844     return self._exec_insertmany_context(dialect, context)
   1845 else:
-> 1846     return self._exec_single_context(
   1847         dialect, context, statement, parameters
   1848     )

File c:\Users\ASUS\Documents\Project\IIabm\venv\lib\site-packages\sqlalchemy\engine\base.py:1986, in Connection._exec_single_context(self, dialect, context, statement, parameters)
   1983     result = context._setup_result_proxy()
   1985 except BaseException as e:
-> 1986     self._handle_dbapi_exception(
   1987         e, str_statement, effective_parameters, cursor, context
   1988     )
   1990 return result

File c:\Users\ASUS\Documents\Project\IIabm\venv\lib\site-packages\sqlalchemy\engine\base.py:2366, in Connection._handle_dbapi_exception(self, e, statement, parameters, cursor, context, is_sub_exec)
   2364     else:
   2365         assert exc_info[1] is not None
-> 2366         raise exc_info[1].with_traceback(exc_info[2])
   2367 finally:
   2368     del self._reentrant_error

File c:\Users\ASUS\Documents\Project\IIabm\venv\lib\site-packages\sqlalchemy\engine\base.py:1967, in Connection._exec_single_context(self, dialect, context, statement, parameters)
   1965                 break
   1966     if not evt_handled:
-> 1967         self.dialect.do_execute(
   1968             cursor, str_statement, effective_parameters, context
   1969         )
   1971 if self._has_events or self.engine._has_events:
   1972     self.dispatch.after_cursor_execute(
   1973         self,
   1974         cursor,
   (...)
   1978         context.executemany,
   1979     )

File c:\Users\ASUS\Documents\Project\IIabm\venv\lib\site-packages\sqlalchemy\engine\default.py:952, in DefaultDialect.do_execute(self, cursor, statement, parameters, context)
    951 def do_execute(self, cursor, statement, parameters, context=None):
--> 952     cursor.execute(statement, parameters)

File c:\Users\ASUS\Documents\Project\IIabm\venv\lib\site-packages\pymysql\cursors.py:153, in Cursor.execute(self, query, args)
    149     pass
    151 query = self.mogrify(query, args)
--> 153 result = self._query(query)
    154 self._executed = query
    155 return result

File c:\Users\ASUS\Documents\Project\IIabm\venv\lib\site-packages\pymysql\cursors.py:322, in Cursor._query(self, q)
    320 conn = self._get_db()
    321 self._clear_result()
--> 322 conn.query(q)
    323 self._do_get_result()
    324 return self.rowcount

File c:\Users\ASUS\Documents\Project\IIabm\venv\lib\site-packages\pymysql\connections.py:575, in Connection.query(self, sql, unbuffered)
    573     sql = sql.encode(self.encoding, "surrogateescape")
    574 self._execute_command(COMMAND.COM_QUERY, sql)
--> 575 self._affected_rows = self._read_query_result(unbuffered=unbuffered)
    576 return self._affected_rows

File c:\Users\ASUS\Documents\Project\IIabm\venv\lib\site-packages\pymysql\connections.py:826, in Connection._read_query_result(self, unbuffered)
    824     result.init_unbuffered_query()
    825 else:
--> 826     result.read()
    827 self._result = result
    828 if result.server_status is not None:

File c:\Users\ASUS\Documents\Project\IIabm\venv\lib\site-packages\pymysql\connections.py:1210, in MySQLResult.read(self)
   1208         self._read_load_local_packet(first_packet)
   1209     else:
-> 1210         self._read_result_packet(first_packet)
   1211 finally:
   1212     self.connection = None

File c:\Users\ASUS\Documents\Project\IIabm\venv\lib\site-packages\pymysql\connections.py:1287, in MySQLResult._read_result_packet(self, first_packet)
   1285 self.field_count = first_packet.read_length_encoded_integer()
   1286 self._get_descriptions()
-> 1287 self._read_rowdata_packet()

File c:\Users\ASUS\Documents\Project\IIabm\venv\lib\site-packages\pymysql\connections.py:1334, in MySQLResult._read_rowdata_packet(self)
   1332 rows = []
   1333 while True:
-> 1334     packet = self.connection._read_packet()
   1335     if self._check_packet_is_eof(packet):
   1336         self.connection = None  # release reference to kill cyclic reference.

File c:\Users\ASUS\Documents\Project\IIabm\venv\lib\site-packages\pymysql\connections.py:751, in Connection._read_packet(self, packet_type)
    749 buff = bytearray()
    750 while True:
--> 751     packet_header = self._read_bytes(4)
    752     # if DEBUG: dump_packet(packet_header)
    754     btrl, btrh, packet_number = struct.unpack("<HBB", packet_header)

File c:\Users\ASUS\Documents\Project\IIabm\venv\lib\site-packages\pymysql\connections.py:789, in Connection._read_bytes(self, num_bytes)
    787 while True:
    788     try:
--> 789         data = self._rfile.read(num_bytes)
    790         break
    791     except OSError as e:

File ~\AppData\Local\Programs\Python\Python38\lib\socket.py:669, in SocketIO.readinto(self, b)
    667 while True:
    668     try:
--> 669         return self._sock.recv_into(b)
    670     except timeout:
    671         self._timeout_occurred = True

KeyboardInterrupt: 
In [8]:
import json
import networkx as nx

# Function to convert string back to networkx graph
def string_to_graph(g_string):
    """Convert JSON string back to networkx graph"""
    if pd.isna(g_string):
        return None
    g_data = json.loads(g_string)
    return nx.adjacency_graph(g_data)

# Option 3: Comprehensive firm metrics for MultiDiGraph
def get_comprehensive_firm_metrics(g_string, node_id):
    """Get both direct MultiDiGraph metrics and weighted graph metrics"""
    if pd.isna(g_string):
        return {col: None for col in [
            'num_supplier_firms', 'num_customer_firms',
            'num_products_supplied', 'num_products_received',
            'weighted_betweenness', 'weighted_closeness', 'pagerank'
        ]}
    
    G_multi = string_to_graph(g_string)
    
    if node_id not in G_multi.nodes():
        return {col: None for col in [
            'num_supplier_firms', 'num_customer_firms',
            'num_products_supplied', 'num_products_received',
            'weighted_betweenness', 'weighted_closeness', 'pagerank'
        ]}
    
    # Direct MultiDiGraph metrics
    num_suppliers = G_multi.in_degree(node_id)
    num_customers = G_multi.out_degree(node_id)
    num_products_in = len(list(G_multi.in_edges(node_id)))
    num_products_out = len(list(G_multi.out_edges(node_id)))
    
    # Convert to weighted DiGraph for advanced metrics
    G_weighted = nx.DiGraph()
    for u, v in G_multi.edges():
        if G_weighted.has_edge(u, v):
            G_weighted[u][v]['weight'] += 1
        else:
            G_weighted.add_edge(u, v, weight=1)
    G_weighted.add_nodes_from(G_multi.nodes())
    
    # Calculate centrality on weighted graph
    betweenness = nx.betweenness_centrality(G_weighted, weight='weight')
    closeness = nx.closeness_centrality(G_weighted, distance='weight')
    pagerank = nx.pagerank(G_weighted, weight='weight')
    
    return {
        'num_supplier_firms': num_suppliers,
        'num_customer_firms': num_customers,
        'num_products_supplied': num_products_out,
        'num_products_received': num_products_in,
        'weighted_betweenness': betweenness.get(node_id),
        'weighted_closeness': closeness.get(node_id),
        'pagerank': pagerank.get(node_id)
    }

# Convert id_firm to string to match graph node IDs
count_with_gfirm['id_firm_str'] = count_with_gfirm['id_firm'].astype(str)

# Calculate metrics for each row
print("Calculating firm-level centrality metrics...")
firm_metrics = count_with_gfirm.apply(
    lambda row: pd.Series(get_comprehensive_firm_metrics(row['g_firm'], row['id_firm_str'])),
    axis=1
)
firm_metrics.columns = ['firm_' + col for col in firm_metrics.columns]

# Add the metrics as new columns
count_with_gfirm = pd.concat([count_with_gfirm, firm_metrics], axis=1)

print("\nColumns in final dataframe:")
print(count_with_gfirm.columns.tolist())
count_with_gfirm.head()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[8], line 62
     51     return {
     52         'num_supplier_firms': num_suppliers,
     53         'num_customer_firms': num_customers,
   (...)
     58         'pagerank': pagerank.get(node_id)
     59     }
     61 # Convert id_firm to string to match graph node IDs
---> 62 count_with_gfirm['id_firm_str'] = count_with_gfirm['id_firm'].astype(str)
     64 # Calculate metrics for each row
     65 print("Calculating firm-level centrality metrics...")

NameError: name 'count_with_gfirm' is not defined
In [9]:
firm = pd.read_csv('Firm.csv')
count_with_gfirm = count_with_gfirm.merge(firm[['Code', 'Assets', 'Revenue', 'Size']], how='left', left_on='id_firm', right_on='Code').drop(columns=['Code'])
count_with_gfirm.to_csv('count_with_gfirm.csv', index=False)
count_with_gfirm.head()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[9], line 2
      1 firm = pd.read_csv('Firm.csv')
----> 2 count_with_gfirm = count_with_gfirm.merge(firm[['Code', 'Assets', 'Revenue', 'Size']], how='left', left_on='id_firm', right_on='Code').drop(columns=['Code'])
      3 count_with_gfirm.to_csv('count_with_gfirm.csv', index=False)
      4 count_with_gfirm.head()

NameError: name 'count_with_gfirm' is not defined
In [11]:
# Group by firm and aggregate
import pandas as pd
count_with_gfirm = pd.read_csv('count_with_gfirm.csv')
firm_summary = count_with_gfirm.groupby('id_firm').agg({
    # Count number of disruptions
    'ts': 'count',  # Will rename to 'count'
    
    # Average of network metrics
    'firm_num_supplier_firms': 'mean',
    'firm_num_customer_firms': 'mean',
    'firm_num_products_supplied': 'mean',
    'firm_num_products_received': 'mean',
    'firm_weighted_betweenness': 'mean',
    'firm_weighted_closeness': 'mean',
    'firm_pagerank': 'mean',
    
    # First element of firm attributes (should be same within group)
    'Assets': 'first',
    'Revenue': 'first',
    'Size': 'first'
}).rename(columns={'ts': 'count'})

# Reset index to make id_firm a column
firm_summary = firm_summary.reset_index()

print(f"Total number of firms: {len(firm_summary)}")
print(f"\nColumns: {firm_summary.columns.tolist()}")
print(f"\nSummary statistics:")
print(firm_summary.describe())

firm_summary.head(10)
Out [11]:
Total number of firms: 171

Columns: ['id_firm', 'count', 'firm_num_supplier_firms', 'firm_num_customer_firms', 'firm_num_products_supplied', 'firm_num_products_received', 'firm_weighted_betweenness', 'firm_weighted_closeness', 'firm_pagerank', 'Assets', 'Revenue', 'Size']

Summary statistics:
          id_firm        count  firm_num_supplier_firms  \
count  171.000000   171.000000               171.000000   
mean    85.000000   186.672515                 3.190330   
std     49.507575   303.402661                 7.900570   
min      0.000000    17.000000                 0.000000   
25%     42.500000    33.000000                 0.000000   
50%     85.000000    51.000000                 0.000000   
75%    127.500000   189.000000                 0.000000   
max    170.000000  1955.000000                47.204703   

       firm_num_customer_firms  firm_num_products_supplied  \
count               171.000000                  171.000000   
mean                  3.398434                    3.398434   
std                   5.946080                    5.946080   
min                   0.000000                    0.000000   
25%                   1.159220                    1.159220   
50%                   1.794118                    1.794118   
75%                   2.803584                    2.803584   
max                  51.010654                   51.010654   

       firm_num_products_received  firm_weighted_betweenness  \
count                  171.000000                 171.000000   
mean                     3.190330                   0.000365   
std                      7.900570                   0.001466   
min                      0.000000                   0.000000   
25%                      0.000000                   0.000000   
50%                      0.000000                   0.000000   
75%                      0.000000                   0.000000   
max                     47.204703                   0.013216   

       firm_weighted_closeness  firm_pagerank        Assets       Revenue  
count               171.000000     171.000000  1.020000e+02  1.110000e+02  
mean                  0.020191       0.005886  2.773894e+11  1.578147e+11  
std                   0.054180       0.014406  5.238742e+11  3.774420e+11  
min                   0.000000       0.001817  1.560000e+08  4.545320e+07  
25%                   0.000000       0.001925  3.043750e+09  1.318000e+09  
50%                   0.000000       0.002247  1.808500e+10  6.894667e+09  
75%                   0.000000       0.002283  3.432049e+11  1.641537e+11  
max                   0.287842       0.141979  2.893377e+12  3.232375e+12  
id_firm count firm_num_supplier_firms firm_num_customer_firms firm_num_products_supplied firm_num_products_received firm_weighted_betweenness firm_weighted_closeness firm_pagerank Assets Revenue Size
0 0 352 7.863636 0.823864 0.823864 7.863636 0.00102 0.046257 0.008342 4.204000e+10 1.089000e+10 L
1 1 31 0.000000 1.935484 1.935484 0.000000 0.00000 0.000000 0.001823 5.240000e+08 1.380000e+08 M
2 2 33 0.000000 1.909091 1.909091 0.000000 0.00000 0.000000 0.001825 NaN NaN M
3 3 124 0.000000 9.169355 9.169355 0.000000 0.00000 0.000000 0.001821 NaN NaN S
4 4 34 0.000000 2.411765 2.411765 0.000000 0.00000 0.000000 0.001824 NaN NaN M
5 5 128 0.000000 4.000000 4.000000 0.000000 0.00000 0.000000 0.001823 NaN NaN S
6 6 148 0.000000 8.527027 8.527027 0.000000 0.00000 0.000000 0.001823 9.020000e+08 1.590000e+08 M
7 7 24 0.000000 1.625000 1.625000 0.000000 0.00000 0.000000 0.001824 NaN NaN L
8 8 30 0.000000 1.000000 1.000000 0.000000 0.00000 0.000000 0.001821 NaN NaN M
9 9 65 0.000000 4.861538 4.861538 0.000000 0.00000 0.000000 0.001823 NaN NaN L
In [12]:
import matplotlib.pyplot as plt
import numpy as np
from scipy.stats import pearsonr

# Metrics to plot (all columns except id_firm, count, and Size)

metrics = [
    'firm_num_supplier_firms',
    'firm_num_customer_firms', 
    'firm_num_products_supplied',
    'firm_num_products_received',
    'firm_weighted_betweenness',
    'firm_weighted_closeness',
    'firm_pagerank',
    'Assets',
    'Revenue'
]

# Create a grid of subplots (3 rows x 3 columns)
fig, axes = plt.subplots(3, 3, figsize=(15, 12))
fig.suptitle('Disruption Count vs Firm Metrics', fontsize=16, y=0.995)

# Flatten axes array for easier iteration
axes = axes.flatten()

# Plot each metric
for idx, metric in enumerate(metrics):
    ax = axes[idx]
    
    # Remove NaN values for plotting
    plot_data = firm_summary[['count', metric]].dropna()
    
    # Scatter plot
    ax.scatter(plot_data['count'], plot_data[metric], alpha=0.6, s=50)
    
    # Add trend line
    if len(plot_data) > 1:
        z = np.polyfit(plot_data['count'], plot_data[metric], 1)
        p = np.poly1d(z)
        x_trend = np.linspace(plot_data['count'].min(), plot_data['count'].max(), 100)
        ax.plot(x_trend, p(x_trend), "r--", alpha=0.8, linewidth=2)
    
    # Labels and formatting
    ax.set_xlabel('Count (Number of Disruptions)', fontsize=10)
    ax.set_ylabel(metric.replace('firm_', '').replace('_', ' ').title(), fontsize=10)
    ax.grid(True, alpha=0.3)
    
    # Calculate and display correlation
    if len(plot_data) > 1:
        corr = plot_data['count'].corr(plot_data[metric])
        ax.text(0.05, 0.95, f'r = {corr:.3f}', 
                transform=ax.transAxes, 
                verticalalignment='top',
                bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))

plt.tight_layout()
plt.show()

# Print summary statistics
print("\nCorrelation with disruption count:")
print(f"{'Metric':<35} {'r':>8} {'p-value':>10} {'n':>6}")
print("="*62)
for metric in metrics:
    if metric in firm_summary.columns:
        plot_data = firm_summary[['count', metric]].dropna()
        if len(plot_data) > 1:
            r, p_value = pearsonr(plot_data['count'], plot_data[metric])
            n = len(plot_data)
            print(f"{metric:<35} {r:8.4f} {p_value:10.4e} {n:6d}")
Correlation with disruption count:
Metric                                     r    p-value      n
==============================================================
firm_num_supplier_firms               0.8648 1.9185e-52    171
firm_num_customer_firms               0.2781 2.3045e-04    171
firm_num_products_supplied            0.2781 2.3045e-04    171
firm_num_products_received            0.8648 1.9185e-52    171
firm_weighted_betweenness             0.7633 6.8804e-34    171
firm_weighted_closeness               0.7400 6.4124e-31    171
firm_pagerank                         0.7519 2.1191e-32    171
Assets                                0.3723 1.1656e-04    102
Revenue                               0.2302 1.5081e-02    111
In [13]:
import matplotlib.pyplot as plt
import matplotlib
import numpy as np

# 设置中文字体为宋体
matplotlib.rcParams['font.sans-serif'] = ['SimSun']  # 宋体
matplotlib.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题

# 选择的指标
selected_metrics = [
    ('firm_weighted_betweenness', '网络中介性(Betweenness)'),
    ('firm_pagerank', '系统重要性(PageRank)'),
    ('firm_num_supplier_firms', '连接广度(上游供应商数量)'),
    ('Revenue', '规模属性(营业收入)')
]

# 创建一行四列的子图,共享y轴
fig, axes = plt.subplots(1, 4, figsize=(12, 4), dpi=300, sharey=True)

# 绘制每个指标
for idx, (metric, chinese_label) in enumerate(selected_metrics):
    ax = axes[idx]
    
    # 移除NaN值
    plot_data = firm_summary[['count', metric]].dropna()
    
    # 散点图(指标为x轴,count为y轴)
    ax.scatter(plot_data[metric], plot_data['count'], alpha=0.6, s=50)
    
    # 添加回归线(黑色虚线)
    if len(plot_data) > 1:
        z = np.polyfit(plot_data[metric], plot_data['count'], 1)
        p = np.poly1d(z)
        x_trend = np.linspace(plot_data[metric].min(), plot_data[metric].max(), 100)
        ax.plot(x_trend, p(x_trend), "k--", alpha=0.8, linewidth=2)  # 黑色虚线
    
    # 设置标签
    ax.set_xlabel(chinese_label, fontsize=12)
    # 只在第一个子图显示y轴标签
    if idx == 0:
        ax.set_ylabel('级联失效频率', fontsize=12)
    ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()
In [ ]:
In [ ]: