{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "                            OLS Regression Results                            \n",
      "==============================================================================\n",
      "Dep. Variable:            Revenue_Log   R-squared:                       0.586\n",
      "Model:                            OLS   Adj. R-squared:                  0.580\n",
      "Method:                 Least Squares   F-statistic:                     94.86\n",
      "Date:                Sat, 01 Apr 2023   Prob (F-statistic):           1.85e-14\n",
      "Time:                        18:07:54   Log-Likelihood:                -128.58\n",
      "No. Observations:                  69   AIC:                             261.2\n",
      "Df Residuals:                      67   BIC:                             265.6\n",
      "Df Model:                           1                                         \n",
      "Covariance Type:            nonrobust                                         \n",
      "==================================================================================\n",
      "                     coef    std err          t      P>|t|      [0.025      0.975]\n",
      "----------------------------------------------------------------------------------\n",
      "Intercept         14.3160      0.806     17.757      0.000      12.707      15.925\n",
      "Num_Employ_Log     0.9728      0.100      9.740      0.000       0.773       1.172\n",
      "==============================================================================\n",
      "Omnibus:                       55.594   Durbin-Watson:                   1.958\n",
      "Prob(Omnibus):                  0.000   Jarque-Bera (JB):              244.671\n",
      "Skew:                           2.451   Prob(JB):                     7.42e-54\n",
      "Kurtosis:                      10.815   Cond. No.                         34.6\n",
      "==============================================================================\n",
      "\n",
      "Notes:\n",
      "[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.\n",
      "['1', '1.1', '1.2', '1.3', '1.4', '2', '1.1.1', '1.1.2', '1.1.3', '1.2.1', '1.2.2', '1.2.3', '1.3.1', '1.3.2', '1.3.3', '1.3.4', '1.3.5', '1.4.1', '1.4.2', '1.4.3', '1.4.4', '1.4.5', '2.1', '2.2', '2.3', '1.3.1.1', '1.3.1.2', '1.3.1.3', '1.3.1.4', '1.3.1.5', '1.3.1.6', '1.3.1.7', '1.3.2.1', '1.3.3.1', '1.3.3.2', '1.3.3.3', '1.3.3.4', '1.3.3.5', '1.3.3.6', '1.3.3.7', '1.3.4.1', '1.3.4.2', '1.3.4.3', '1.3.5.1', '1.4.1.1', '1.4.1.2', '1.4.1.3', '1.4.1.4', '1.4.1.5', '1.4.2.1', '1.4.2.2', '1.4.2.3', '1.4.2.4', '1.4.2.5', '1.4.2.6', '1.4.2.7', '1.4.3.1', '1.4.3.2', '1.4.3.3', '1.4.3.4', '1.4.3.5', '1.4.3.6', '1.4.4.1', '1.4.4.2', '1.4.4.3', '1.4.4.4', '1.4.4.5', '1.4.5.1', '1.4.5.2', '1.4.5.3', '1.4.5.4', '1.4.5.5', '1.4.5.6', '1.4.5.7', '1.4.5.8', '1.4.5.9', '2.1.1', '2.1.2', '2.1.3', '2.1.4', '2.3.1', '2.3.2', '2.3.3', '2.1.1.1', '2.1.1.2', '2.1.1.3', '2.1.1.4', '2.1.1.5', '2.1.2.1', '2.1.2.2', '2.1.2.3', '2.1.2.4', '2.1.3.1', '2.1.3.2', '2.1.3.3', '2.1.3.4', '2.1.3.5', '2.1.3.6', '2.1.3.7', '2.1.4.1', '2.1.4.2', '2.1.4.1.1', '2.1.4.1.2', '2.1.4.1.3', '2.1.4.1.4', '2.1.4.2.1', '2.1.4.2.2']\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "C:\\Users\\ASUS\\AppData\\Local\\Temp\\ipykernel_17316\\1464358194.py:49: PerformanceWarning: DataFrame is highly fragmented.  This is usually the result of calling `frame.insert` many times, which has poor performance.  Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n",
      "  Firm_copy.insert(Firm_copy.columns.get_loc('Revenue'),'Revenue_Log', series_Revenue_Log)\n",
      "C:\\Users\\ASUS\\AppData\\Local\\Temp\\ipykernel_17316\\1464358194.py:51: PerformanceWarning: DataFrame is highly fragmented.  This is usually the result of calling `frame.insert` many times, which has poor performance.  Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n",
      "  Firm_copy.insert(Firm_copy.columns.get_loc('Num_Employ'),'Num_Employ_Log', series_Num_Employ_Log)\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 42148 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "import math\n",
    "import statsmodels.formula.api as smf\n",
    "from sklearn.preprocessing import MinMaxScaler\n",
    "import networkx as nx\n",
    "import matplotlib.pyplot as plt\n",
    "import seaborn as sns\n",
    "\n",
    "plt.rcParams['font.sans-serif'] = 'SimHei'\n",
    "\n",
    "# init graph bom\n",
    "BomNodes = pd.read_csv('BomNodes.csv', index_col=0)\n",
    "BomNodes.set_index('Code', inplace=True)\n",
    "BomCateNet = pd.read_csv('BomCateNet.csv', index_col=0)\n",
    "BomCateNet.fillna(0, inplace=True)\n",
    "\n",
    "G = nx.from_pandas_adjacency(BomCateNet, create_using=nx.MultiDiGraph())\n",
    "\n",
    "labels_dict = {}\n",
    "for code in G.nodes:\n",
    "    labels_dict[code] = BomNodes.loc[code].to_dict()\n",
    "nx.set_node_attributes(G, labels_dict)\n",
    "\n",
    "# load firm\n",
    "Firm = pd.read_csv(\"Firm.csv\")\n",
    "Firm_copy = Firm.copy()\n",
    "\n",
    "# add attribute, number of firm, to graph bom\n",
    "firm_num_dict = {}\n",
    "for node in nx.nodes(G):\n",
    "    firm_num_dict[node]= sum(Firm_copy[node]==1)\n",
    "nx.set_node_attributes(G, firm_num_dict, name=\"Num_Firm\")\n",
    "\n",
    "# get log revenue\n",
    "Firm_copy['Revenue_Log'] = Firm_copy['Revenue'].map(math.log)\n",
    "Firm_copy['Num_Employ_Log'] = Firm_copy['Num_Employ'].map(math.log)\n",
    "data_ols = Firm_copy[Firm_copy[['Revenue_Log', 'Num_Employ_Log']].notnull().all(axis=1)][['Revenue_Log', 'Num_Employ_Log']]\n",
    "\n",
    "ols_model = smf.ols('Revenue_Log ~ Num_Employ_Log', data=data_ols)\n",
    "ols_results = ols_model.fit()\n",
    "b = ols_results.params.Intercept\n",
    "a = ols_results.params.Num_Employ_Log\n",
    "print(ols_results.summary())\n",
    "sns.regplot(x='Num_Employ_Log',y='Revenue_Log',data=data_ols[data_ols.notnull().all(axis=1)])\n",
    "\n",
    "Firm_copy.loc[Firm_copy['Revenue_Log'].isnull(), 'Revenue_Log'] = Firm_copy[Firm_copy['Revenue_Log'].isnull()]['Num_Employ_Log'].map(lambda x: a*x + b)\n",
    "series_Revenue_Log = Firm_copy.pop('Revenue_Log')\n",
    "Firm_copy.insert(Firm_copy.columns.get_loc('Revenue'),'Revenue_Log', series_Revenue_Log)\n",
    "series_Num_Employ_Log = Firm_copy.pop('Num_Employ_Log')\n",
    "Firm_copy.insert(Firm_copy.columns.get_loc('Num_Employ'),'Num_Employ_Log', series_Num_Employ_Log)\n",
    "\n",
    "# lift firm from neighboring tier\n",
    "node_list = list(nx.bfs_tree(G, '1'))\n",
    "node_list.reverse()\n",
    "\n",
    "# init criterion for ending the lifting process\n",
    "node_check = [k for k, v in nx.get_node_attributes(G, 'Num_Firm').items() if v==0]\n",
    "if '1' in node_check:\n",
    "    node_check.remove('1')\n",
    "for node in node_check:\n",
    "    if G.out_degree(node) == 0:\n",
    "        node_check.remove(node)\n",
    "        print(f\"Warning: node, {node}, in tier 0 does not contain firm\")\n",
    "\n",
    "while node_check:\n",
    "    for node in node_list[:-1]:\n",
    "        if G.out_degree(node) > 0 and sum(Firm_copy[node]==1) == 0:\n",
    "            list_neighbors = list(G.neighbors(node))\n",
    "            firm_list = Firm_copy.index[(Firm_copy[list_neighbors]==1).all(axis=1)].to_list()\n",
    "            if firm_list: # there exist firm that produces all components\n",
    "                # lift firm with size above average in firm_list\n",
    "                average_size = Firm_copy.loc[firm_list, 'Revenue_Log'].mean()\n",
    "                firm_selected_list = Firm_copy.loc[firm_list].loc[Firm_copy.loc[firm_list, 'Revenue_Log'] >= average_size].index.to_list()\n",
    "                Firm_copy.loc[firm_selected_list, node] = 1\n",
    "                Firm_copy.loc[firm_selected_list, list_neighbors] = np.nan\n",
    "            else: # select top 15% firm in terms of size\n",
    "                firm_list = Firm_copy.index[(Firm_copy[list_neighbors]==1).any(axis=1)].to_list()\n",
    "                firm_df = Firm_copy.loc[firm_list].sort_values('Revenue_Log', ascending=False)\n",
    "                num_firm_selected = round(firm_df.shape[0] * 0.15)\n",
    "                firm_selected_list = firm_df.index[0: num_firm_selected].to_list()\n",
    "                Firm_copy.loc[firm_selected_list, node] = 1\n",
    "                Firm_copy.loc[firm_selected_list, list_neighbors] = np.nan\n",
    "\n",
    "    # update attribute, number of firm\n",
    "    for node in nx.nodes(G):\n",
    "        firm_num_dict[node]= sum(Firm_copy[node]==1)\n",
    "    nx.set_node_attributes(G, firm_num_dict, name=\"Num_Firm\")\n",
    "\n",
    "    # update criterion for ending the lifting process\n",
    "    node_check = [k for k, v in nx.get_node_attributes(G, 'Num_Firm').items() if v==0]\n",
    "    if '1' in node_check:\n",
    "        node_check.remove('1')\n",
    "    for node in node_check:\n",
    "        if G.out_degree(node) == 0:\n",
    "            node_check.remove(node)\n",
    "            print(f\"Warning: node, {node}, in tier 0 does not contain firm\")\n",
    "\n",
    "# output\n",
    "# Firm_copy.to_csv('Firm_amended.csv', index=False, encoding='utf-8-sig')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 1314844 characters]",
      "text/plain": [
       "<Figure size 3600x3600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# visualization\n",
    "pos = nx.nx_agraph.graphviz_layout(G, prog=\"twopi\", args=\"\")\n",
    "dict_num_firm = nx.get_node_attributes(G, 'Num_Firm')\n",
    "dict_node_name = nx.get_node_attributes(G, 'Name')\n",
    "node_labels = {}\n",
    "for node in nx.nodes(G):\n",
    "    node_labels[node] = f\"{node} {str(dict_node_name[node])} {str(dict_num_firm[node])}\"\n",
    "    # node_labels[node] = f\"{str(dict_num_firm[node])}\"\n",
    "plt.figure(figsize=(12, 12), dpi=300)\n",
    "nx.draw_networkx_nodes(G, pos)\n",
    "nx.draw_networkx_edges(G, pos)\n",
    "nx.draw_networkx_labels(G, pos, labels = node_labels, font_size=4)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0       7393.0\n",
      "1        171.0\n",
      "2        113.0\n",
      "3         24.0\n",
      "4        242.0\n",
      "        ...   \n",
      "165       60.0\n",
      "166      292.0\n",
      "167    13371.0\n",
      "168     5057.0\n",
      "169     5173.0\n",
      "Name: Num_Employ, Length: 129, dtype: float64\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<seaborn.axisgrid.FacetGrid at 0x2bd702fc820>"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 13736 characters]",
      "text/plain": [
       "<Figure size 500x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import seaborn as sns\n",
    "data = Firm_copy[Firm_copy['Num_Employ'] > 0]['Num_Employ']\n",
    "print(data)\n",
    "sns.displot(data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0      1.089000e+10\n",
      "1      1.380000e+08\n",
      "6      1.590000e+08\n",
      "11     6.750000e+08\n",
      "13     2.470000e+09\n",
      "           ...     \n",
      "162    5.880760e+07\n",
      "167    4.110000e+10\n",
      "168    4.519000e+09\n",
      "169    3.470400e+10\n",
      "170    1.000000e+08\n",
      "Name: Revenue, Length: 111, dtype: float64\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<seaborn.axisgrid.FacetGrid at 0x2bd17758a00>"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 13852 characters]",
      "text/plain": [
       "<Figure size 500x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import seaborn as sns\n",
    "data = Firm_copy[Firm_copy['Revenue'] > 0]['Revenue']\n",
    "print(data)\n",
    "sns.displot(data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.13"
  },
  "orig_nbformat": 4,
  "vscode": {
   "interpreter": {
    "hash": "a90aeebcf29d64a654773811cc170cb25061cb2498f10ac689db374c7bf325de"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
