DavMelchi commited on
Commit
b89c5d7
·
1 Parent(s): 3bd7249

Gsm capacity improvement with columns naming, alignment and colors

Browse files
apps/kpi_analysis/gsm_capacity.py CHANGED
@@ -112,12 +112,13 @@ if (
112
  daily_kpi_df: pd.DataFrame = dfs[2]
113
  distance_df: pd.DataFrame = dfs[3]
114
  GsmCapacity.final_results = convert_gsm_dfs(
115
- [gsm_analysis_df, bh_kpi_df, daily_kpi_df, distance_df],
116
- ["GSM_Analysis", "BH_KPI_Analysis", "Daily_KPI_Analysis", "Distance"],
117
  )
118
 
119
  # GsmCapacity.final_results = convert_gsm_dfs(
120
- # [gsm_analysis_df], ["GSM_Analysis"]
 
121
  # )
122
 
123
  if GsmCapacity.final_results is not None:
@@ -142,6 +143,18 @@ if (
142
  st.plotly_chart(fig, use_container_width=True)
143
  st.write(final_comments_df)
144
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  # Add dataframe and ploty bar chart with "BH Congestion status" distribution in gsm_analysis_df in 2 columns
146
  bh_congestion_status_df = (
147
  gsm_analysis_df.groupby("BH Congestion status")
 
112
  daily_kpi_df: pd.DataFrame = dfs[2]
113
  distance_df: pd.DataFrame = dfs[3]
114
  GsmCapacity.final_results = convert_gsm_dfs(
115
+ [gsm_analysis_df, distance_df, bh_kpi_df, daily_kpi_df],
116
+ ["GSM_Analysis", "Distance", "BH_KPI_Analysis", "Daily_KPI_Analysis"],
117
  )
118
 
119
  # GsmCapacity.final_results = convert_gsm_dfs(
120
+ # [gsm_analysis_df, bh_kpi_df, daily_kpi_df],
121
+ # ["GSM_Analysis", "BH_KPI_Analysis", "Daily_KPI_Analysis"],
122
  # )
123
 
124
  if GsmCapacity.final_results is not None:
 
143
  st.plotly_chart(fig, use_container_width=True)
144
  st.write(final_comments_df)
145
 
146
+ # Add dataframe and ploty bar chart with "Final comment summary" distribution in gsm_analysis_df in 2 columns
147
+ final_comments_summary_df = (
148
+ gsm_analysis_df.groupby("Final comment summary")
149
+ .size()
150
+ .reset_index(name="count")
151
+ )
152
+ fig = px.bar(final_comments_summary_df, x="Final comment summary", y="count")
153
+ fig.update_layout(height=1000)
154
+ fig.update_traces(texttemplate="%{value}", textposition="outside")
155
+ st.plotly_chart(fig, use_container_width=True)
156
+ st.write(final_comments_summary_df)
157
+
158
  # Add dataframe and ploty bar chart with "BH Congestion status" distribution in gsm_analysis_df in 2 columns
159
  bh_congestion_status_df = (
160
  gsm_analysis_df.groupby("BH Congestion status")
documentations/gsm_capacity_docs.py CHANGED
@@ -144,7 +144,7 @@ results = analyze_gsm_data(
144
  | Target HR CHs | Calculated target number of Half Rate channels needed. |
145
  | Target TCHs | Total target Traffic Channels (FR + HR) required for desired performance. |
146
  | Target TRXs | Target number of TRXs required, based on channel requirements and configuration. |
147
- | Numberof required TRXs | Final computed number of TRXs required to meet traffic and blocking targets. |
148
  | operational_comment | Generated operational comment based on analysis (e.g., upgrade needed, OK, etc.). |
149
  | Final comment | Final summary comment combining all relevant flags, operational status, and recommendations. |
150
 
 
144
  | Target HR CHs | Calculated target number of Half Rate channels needed. |
145
  | Target TCHs | Total target Traffic Channels (FR + HR) required for desired performance. |
146
  | Target TRXs | Target number of TRXs required, based on channel requirements and configuration. |
147
+ | Number of required TRXs | Final computed number of TRXs required to meet traffic and blocking targets. |
148
  | operational_comment | Generated operational comment based on analysis (e.g., upgrade needed, OK, etc.). |
149
  | Final comment | Final summary comment combining all relevant flags, operational status, and recommendations. |
150
 
process_kpi/process_gsm_capacity.py CHANGED
@@ -7,6 +7,7 @@ from utils.check_sheet_exist import execute_checks_sheets_exist
7
  from utils.convert_to_excel import convert_dfs, save_dataframe
8
  from utils.kpi_analysis_utils import (
9
  GsmAnalysis,
 
10
  analyze_sdcch_call_blocking,
11
  analyze_tch_abis_fails,
12
  analyze_tch_call_blocking,
@@ -18,11 +19,80 @@ from utils.kpi_analysis_utils import (
18
  kpi_naming_cleaning,
19
  )
20
 
21
-
22
- class GsmCapacity:
23
- final_results = None
24
- operational_neighbours_df = None
25
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
  OPERATIONAL_NEIGHBOURS_COLUMNS = [
28
  "ID_BTS",
@@ -461,11 +531,27 @@ def get_operational_neighbours(distance: int) -> pd.DataFrame:
461
  distances_df = distances_dfs[0]
462
  df1 = distances_df[distances_df["Distance_km"] <= distance]
463
 
464
- # save_dataframe(operational_df, "Operational Neighbours")
465
- # save_dataframe(congested_df, "Congested Neighbours")
466
- # # save_dataframe(distances_df, "Distances")
467
- # save_dataframe(df1, "Closest Neighbours")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
468
 
 
 
469
  return df1
470
 
471
 
@@ -560,8 +646,8 @@ def analyze_gsm_data(
560
  gsm_analysis_df["Target TCHs"] / 8
561
  ) # df["Target TCHs"] / 8
562
 
563
- # "Numberof required TRXs" equal to difference between "Target TRXs" and "number_trx_per_cell"
564
- gsm_analysis_df["Numberof required TRXs"] = (
565
  gsm_analysis_df["Target TRXs"] - gsm_analysis_df["number_trx_per_cell"]
566
  )
567
 
@@ -602,6 +688,11 @@ def analyze_gsm_data(
602
  "operational_comment",
603
  new_column="Final comment",
604
  )
 
 
 
 
 
605
 
606
  GsmCapacity.operational_neighbours_df = gsm_analysis_df[
607
  OPERATIONAL_NEIGHBOURS_COLUMNS
@@ -609,3 +700,4 @@ def analyze_gsm_data(
609
  distance_df = get_operational_neighbours(operational_neighbours_distance)
610
 
611
  return [gsm_analysis_df, bh_kpi_full_df, daily_kpi_full_df, distance_df]
 
 
7
  from utils.convert_to_excel import convert_dfs, save_dataframe
8
  from utils.kpi_analysis_utils import (
9
  GsmAnalysis,
10
+ GsmCapacity,
11
  analyze_sdcch_call_blocking,
12
  analyze_tch_abis_fails,
13
  analyze_tch_call_blocking,
 
19
  kpi_naming_cleaning,
20
  )
21
 
22
+ GSM_ANALYSIS_COLUMNS = [
23
+ "ID_BTS",
24
+ "site_name",
25
+ "name",
26
+ "BSC",
27
+ "BCF",
28
+ "BTS",
29
+ "code",
30
+ "Region",
31
+ "adminState",
32
+ "frequencyBandInUse",
33
+ "cellId",
34
+ "band",
35
+ "site_config_band",
36
+ "trxRfPower",
37
+ "BCCH",
38
+ "Longitude",
39
+ "Latitude",
40
+ "TRX_TCH",
41
+ "MAL_TCH",
42
+ "amrSegLoadDepTchRateLower",
43
+ "amrSegLoadDepTchRateUpper",
44
+ "dedicatedGPRScapacity",
45
+ "defaultGPRScapacity",
46
+ "number_trx_per_cell",
47
+ "number_trx_per_bcf",
48
+ "number_tch_per_cell",
49
+ "number_sd_per_cell",
50
+ "number_bcch_per_cell",
51
+ "number_ccch_per_cell",
52
+ "number_cbc_per_cell",
53
+ "number_total_channels_per_cell",
54
+ "number_signals_per_cell",
55
+ "hf_rate_coef",
56
+ "GPRS",
57
+ "TCH Actual HR%",
58
+ "Offered Traffic BH",
59
+ "Max_Traffic BH",
60
+ "Avg_Traffic BH",
61
+ "TCH UTILIZATION (@Max Traffic)",
62
+ "Tch utilization comments",
63
+ "ErlabngB_value",
64
+ "Target FR CHs",
65
+ "Target HR CHs",
66
+ "Target TCHs",
67
+ "Target TRXs",
68
+ "Number of required TRXs",
69
+ "max_tch_call_blocking_bh",
70
+ "avg_tch_call_blocking_bh",
71
+ "number_of_days_with_tch_blocking_exceeded_bh",
72
+ "tch_call_blocking_bh_comment",
73
+ "max_sdcch_real_blocking_bh",
74
+ "avg_sdcch_real_blocking_bh",
75
+ "number_of_days_with_sdcch_blocking_exceeded_bh",
76
+ "sdcch_real_blocking_bh_comment",
77
+ "Average_cell_availability_bh",
78
+ "number_of_days_exceeding_availability_threshold_bh",
79
+ "availability_comment_bh",
80
+ "max_tch_abis_fail_bh",
81
+ "avg_tch_abis_fail_bh",
82
+ "number_of_days_with_tch_abis_fail_exceeded_bh",
83
+ "tch_abis_fail_bh_comment",
84
+ "Average_cell_availability_daily",
85
+ "number_of_days_exceeding_availability_threshold_daily",
86
+ "availability_comment_daily",
87
+ "max_tch_abis_fail_daily",
88
+ "avg_tch_abis_fail_daily",
89
+ "number_of_days_with_tch_abis_fail_exceeded_daily",
90
+ "tch_abis_fail_daily_comment",
91
+ "BH Congestion status",
92
+ "operational_comment",
93
+ "Final comment",
94
+ "Final comment summary",
95
+ ]
96
 
97
  OPERATIONAL_NEIGHBOURS_COLUMNS = [
98
  "ID_BTS",
 
531
  distances_df = distances_dfs[0]
532
  df1 = distances_df[distances_df["Distance_km"] <= distance]
533
 
534
+ # Rename all columns in df1
535
+ df1 = df1.rename(
536
+ columns={
537
+ "Dataset1_ID_BTS": "Source_ID_BTS",
538
+ "Dataset1_name": "Source_name",
539
+ "Dataset1_BH Congestion status": "Source_BH Congestion status",
540
+ "Dataset1_Longitude": "Source_Longitude",
541
+ "Dataset1_Latitude": "Source_Latitude",
542
+ "Dataset2_ID_BTS_Dataset2": "Neighbour_ID_BTS",
543
+ "Dataset2_name_Dataset2": "Neighbour_name",
544
+ "Dataset2_operational_comment_Dataset2": "Neighbour_operational_comment",
545
+ "Dataset2_Longitude_Dataset2": "Neighbour_Longitude",
546
+ "Dataset2_Latitude_Dataset2": "Neighbour_Latitude",
547
+ }
548
+ )
549
+
550
+ # Remove rows if Source_name = Neighbour_name
551
+ df1 = df1[df1["Source_name"] != df1["Neighbour_name"]]
552
 
553
+ # Reset index
554
+ df1 = df1.reset_index(drop=True)
555
  return df1
556
 
557
 
 
646
  gsm_analysis_df["Target TCHs"] / 8
647
  ) # df["Target TCHs"] / 8
648
 
649
+ # "Number of required TRXs" equal to difference between "Target TRXs" and "number_trx_per_cell"
650
+ gsm_analysis_df["Number of required TRXs"] = (
651
  gsm_analysis_df["Target TRXs"] - gsm_analysis_df["number_trx_per_cell"]
652
  )
653
 
 
688
  "operational_comment",
689
  new_column="Final comment",
690
  )
691
+ # Map the final comment using final_comment_mapping
692
+ gsm_analysis_df["Final comment summary"] = gsm_analysis_df["Final comment"].map(
693
+ GsmCapacity.final_comment_mapping
694
+ )
695
+ gsm_analysis_df = gsm_analysis_df[GSM_ANALYSIS_COLUMNS]
696
 
697
  GsmCapacity.operational_neighbours_df = gsm_analysis_df[
698
  OPERATIONAL_NEIGHBOURS_COLUMNS
 
700
  distance_df = get_operational_neighbours(operational_neighbours_distance)
701
 
702
  return [gsm_analysis_df, bh_kpi_full_df, daily_kpi_full_df, distance_df]
703
+ # return [gsm_analysis_df, bh_kpi_full_df, daily_kpi_full_df]
utils/convert_to_excel.py CHANGED
@@ -30,6 +30,9 @@ def get_formats(workbook):
30
  "green": workbook.add_format(
31
  {"bg_color": "#37CC73", "bold": True, "border": 1}
32
  ),
 
 
 
33
  "blue": workbook.add_format({"bg_color": "#1A64FF", "bold": True, "border": 1}),
34
  "blue_light": workbook.add_format(
35
  {"bg_color": "#00B0F0", "bold": True, "border": 1}
@@ -48,6 +51,9 @@ def get_formats(workbook):
48
  ),
49
  "gray": workbook.add_format({"bg_color": "#D9D9D9", "bold": True, "border": 1}),
50
  "red": workbook.add_format({"bg_color": "#FF0000", "bold": True, "border": 1}),
 
 
 
51
  }
52
 
53
 
@@ -59,8 +65,8 @@ def get_format_map_by_format_type(formats: dict, format_type: str) -> dict:
59
  "amrSegLoadDepTchRateUpper": formats["beurre"],
60
  "dedicatedGPRScapacity": formats["beurre"],
61
  "defaultGPRScapacity": formats["beurre"],
62
- "number_trx_per_cell": formats["blue_light"],
63
- "number_trx_per_bcf": formats["blue_light"],
64
  "number_tch_per_cell": formats["blue"],
65
  "number_sd_per_cell": formats["blue"],
66
  "number_bcch_per_cell": formats["blue"],
@@ -74,18 +80,54 @@ def get_format_map_by_format_type(formats: dict, format_type: str) -> dict:
74
  "Offered Traffic BH": formats["green"],
75
  "Max_Traffic BH": formats["green"],
76
  "Avg_Traffic BH": formats["green"],
77
- "Max_tch_call_blocking BH": formats["green"],
78
- "Avg_tch_call_blocking BH": formats["green"],
79
- "number_of_days_with_tch_blocking_exceeded": formats["green"],
80
- "Max_sdcch_real_blocking BH": formats["green"],
81
- "Avg_sdcch_real_blocking BH": formats["green"],
82
- "number_of_days_with_sdcch_blocking_exceeded": formats["green"],
83
- "TCH UTILIZATION (@Max Traffic)": formats["orange"],
84
  "Target FR CHs": formats["purple6"],
85
  "Target HR CHs": formats["purple6"],
86
  "Target TCHs": formats["purple6"],
87
  "Target TRXs": formats["purple6"],
88
- "Numberof required TRXs": formats["purple6"],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  }
90
  elif format_type == "database":
91
  return {
 
30
  "green": workbook.add_format(
31
  {"bg_color": "#37CC73", "bold": True, "border": 1}
32
  ),
33
+ "green_light": workbook.add_format(
34
+ {"bg_color": "#87E0AB", "bold": True, "border": 1}
35
+ ),
36
  "blue": workbook.add_format({"bg_color": "#1A64FF", "bold": True, "border": 1}),
37
  "blue_light": workbook.add_format(
38
  {"bg_color": "#00B0F0", "bold": True, "border": 1}
 
51
  ),
52
  "gray": workbook.add_format({"bg_color": "#D9D9D9", "bold": True, "border": 1}),
53
  "red": workbook.add_format({"bg_color": "#FF0000", "bold": True, "border": 1}),
54
+ "yellow": workbook.add_format(
55
+ {"bg_color": "#FFFF00", "bold": True, "border": 1}
56
+ ),
57
  }
58
 
59
 
 
65
  "amrSegLoadDepTchRateUpper": formats["beurre"],
66
  "dedicatedGPRScapacity": formats["beurre"],
67
  "defaultGPRScapacity": formats["beurre"],
68
+ "number_trx_per_cell": formats["blue"],
69
+ "number_trx_per_bcf": formats["blue"],
70
  "number_tch_per_cell": formats["blue"],
71
  "number_sd_per_cell": formats["blue"],
72
  "number_bcch_per_cell": formats["blue"],
 
80
  "Offered Traffic BH": formats["green"],
81
  "Max_Traffic BH": formats["green"],
82
  "Avg_Traffic BH": formats["green"],
83
+ "TCH UTILIZATION (@Max Traffic)": formats["red"],
84
+ "Tch utilization comments": formats["orange"],
85
+ "ErlabngB_value": formats["purple6"],
 
 
 
 
86
  "Target FR CHs": formats["purple6"],
87
  "Target HR CHs": formats["purple6"],
88
  "Target TCHs": formats["purple6"],
89
  "Target TRXs": formats["purple6"],
90
+ "Number of required TRXs": formats["purple6"],
91
+ "max_tch_call_blocking_bh": formats["yellow"],
92
+ "avg_tch_call_blocking_bh": formats["yellow"],
93
+ "number_of_days_with_tch_blocking_exceeded_bh": formats["yellow"],
94
+ "tch_call_blocking_bh_comment": formats["orange"],
95
+ "max_sdcch_real_blocking_bh": formats["yellow"],
96
+ "avg_sdcch_real_blocking_bh": formats["yellow"],
97
+ "number_of_days_with_sdcch_blocking_exceeded_bh": formats["yellow"],
98
+ "sdcch_real_blocking_bh_comment": formats["orange"],
99
+ "Average_cell_availability_bh": formats["yellow"],
100
+ "number_of_days_exceeding_availability_threshold_bh": formats["yellow"],
101
+ "availability_comment_bh": formats["orange"],
102
+ "max_tch_abis_fail_bh": formats["yellow"],
103
+ "avg_tch_abis_fail_bh": formats["yellow"],
104
+ "number_of_days_with_tch_abis_fail_exceeded_bh": formats["yellow"],
105
+ "tch_abis_fail_bh_comment": formats["orange"],
106
+ "Average_cell_availability_daily": formats["green_light"],
107
+ "number_of_days_exceeding_availability_threshold_daily": formats[
108
+ "green_light"
109
+ ],
110
+ "availability_comment_daily": formats["green_light"],
111
+ "max_tch_abis_fail_daily": formats["green_light"],
112
+ "avg_tch_abis_fail_daily": formats["green_light"],
113
+ "number_of_days_with_tch_abis_fail_exceeded_daily": formats["green_light"],
114
+ "tch_abis_fail_daily_comment": formats["orange"],
115
+ "BH Congestion status": formats["gray"],
116
+ "operational_comment": formats["gray"],
117
+ "Final comment": formats["gray"],
118
+ "Final comment summary": formats["gray"],
119
+ # Operational Neighbours Distance Sheet
120
+ "Source_ID_BTS": formats["blue"],
121
+ "Source_name": formats["blue"],
122
+ "Source_BH Congestion status": formats["blue"],
123
+ "Source_Longitude": formats["blue"],
124
+ "Source_Latitude": formats["blue"],
125
+ "Neighbour_ID_BTS": formats["green_light"],
126
+ "Neighbour_name": formats["green_light"],
127
+ "Neighbour_operational_comment": formats["green_light"],
128
+ "Neighbour_Longitude": formats["green_light"],
129
+ "Neighbour_Latitude": formats["green_light"],
130
+ "Distance_km": formats["beurre"],
131
  }
132
  elif format_type == "database":
133
  return {
utils/kpi_analysis_utils.py CHANGED
@@ -217,6 +217,45 @@ class GsmAnalysis:
217
  }
218
 
219
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  def combine_comments(df: pd.DataFrame, *columns: str, new_column: str) -> pd.DataFrame:
221
  """
222
  Combine comments from multiple columns into one column.
 
217
  }
218
 
219
 
220
+ class GsmCapacity:
221
+ final_results = None
222
+ operational_neighbours_df = None
223
+ final_comment_mapping = {
224
+ "Availability and TX issues": "Operational issues with no congestion",
225
+ "Availability issues": "Operational issues with no congestion",
226
+ "TX issues": "Operational issues with no congestion",
227
+ "Operational is OK": "Operational is OK with no congestion",
228
+ "Tch utilization exceeded threshold, Availability and TX issues": "High utilization with Operational issues",
229
+ "Tch utilization exceeded threshold, Availability issues": "High utilization with Operational issues",
230
+ "Tch utilization exceeded threshold, TX issues": "High utilization with Operational issues",
231
+ "Tch utilization exceeded threshold, SDCCH blocking exceeded threshold, Operational is OK": "High Utilization with Congestion without Operational issues",
232
+ "Tch utilization exceeded threshold, TCH blocking exceeded threshold, Operational is OK": "High Utilization with Congestion without Operational issues",
233
+ "Tch utilization exceeded threshold, TCH blocking exceeded threshold, SDCCH blocking exceeded threshold, Operational is OK": "High Utilization with Congestion without Operational issues",
234
+ "Tch utilization exceeded threshold, TCH blocking exceeded threshold, SDCCH blocking exceeded threshold, TX issues": "High Utilization with Congestion without Operational issues",
235
+ "Tch utilization exceeded threshold, SDCCH blocking exceeded threshold, Availability and TX issues": "High utilization with Congestion and operational issues",
236
+ "Tch utilization exceeded threshold, SDCCH blocking exceeded threshold, TX issues": "High utilization with Congestion and operational issues",
237
+ "Tch utilization exceeded threshold, TCH blocking exceeded threshold, Availability and TX issues": "High utilization with Congestion and operational issues",
238
+ "Tch utilization exceeded threshold, TCH blocking exceeded threshold, Availability issues": "High utilization with Congestion and operational issues",
239
+ "Tch utilization exceeded threshold, TCH blocking exceeded threshold, SDCCH blocking exceeded threshold, Availability and TX issues": "High utilization with Congestion and operational issues",
240
+ "Tch utilization exceeded threshold, TCH blocking exceeded threshold, SDCCH blocking exceeded threshold, Availability issues": "High utilization with Congestion and operational issues",
241
+ "Tch utilization exceeded threshold, TCH blocking exceeded threshold, TX issues": "High utilization with Congestion and operational issues",
242
+ "Down Site": "Down Cell",
243
+ "SDCCH blocking exceeded threshold, Operational is OK": "Congestion without Operational issues",
244
+ "TCH blocking exceeded threshold, Operational is OK": "Congestion without Operational issues",
245
+ "TCH blocking exceeded threshold, SDCCH blocking exceeded threshold, Operational is OK": "Congestion without Operational issues",
246
+ "Tch utilization exceeded threshold, Operational is OK": "High utilization without Congestion and Operational issues",
247
+ "SDCCH blocking exceeded threshold, Availability and TX issues": "Congestion with Operational issues",
248
+ "SDCCH blocking exceeded threshold, Availability issues": "Congestion with Operational issues",
249
+ "SDCCH blocking exceeded threshold, TX issues": "Congestion with Operational issues",
250
+ "TCH blocking exceeded threshold, Availability and TX issues": "Congestion with Operational issues",
251
+ "TCH blocking exceeded threshold, Availability issues": "Congestion with Operational issues",
252
+ "TCH blocking exceeded threshold, SDCCH blocking exceeded threshold, Availability and TX issues": "Congestion with Operational issues",
253
+ "TCH blocking exceeded threshold, SDCCH blocking exceeded threshold, Availability issues": "Congestion with Operational issues",
254
+ "TCH blocking exceeded threshold, SDCCH blocking exceeded threshold, TX issues": "Congestion with Operational issues",
255
+ "TCH blocking exceeded threshold, TX issues": "Congestion with Operational issues",
256
+ }
257
+
258
+
259
  def combine_comments(df: pd.DataFrame, *columns: str, new_column: str) -> pd.DataFrame:
260
  """
261
  Combine comments from multiple columns into one column.